JAL-4386 Fixed some identified bugs in recent changes feature/JAL-4386_calculate_tree_using_secondary_structure_annotation
authorRenia Correya <rcorreya001@dundee.ac.uk>
Tue, 28 May 2024 14:37:13 +0000 (15:37 +0100)
committerRenia Correya <rcorreya001@dundee.ac.uk>
Tue, 28 May 2024 14:37:13 +0000 (15:37 +0100)
29 files changed:
build.gradle
help/help/html/menus/alwedit.html
help/markdown/releases/release-2_11_3_3.md
help/markdown/whatsnew/whatsnew-2_11_3_3.md
resources/lang/Messages.properties
src/jalview/analysis/AAFrequency.java
src/jalview/analysis/AlignmentUtils.java
src/jalview/analysis/scoremodels/SecondaryStructureDistanceModel.java
src/jalview/api/AlignViewControllerI.java
src/jalview/api/AlignViewportI.java
src/jalview/commands/JustifyLeftOrRightCommand.java [new file with mode: 0644]
src/jalview/controller/AlignViewController.java
src/jalview/datamodel/Alignment.java
src/jalview/datamodel/SequenceGroup.java
src/jalview/gui/AlignFrame.java
src/jalview/gui/AlignViewport.java
src/jalview/gui/CalculationChooser.java
src/jalview/gui/PCAPanel.java
src/jalview/gui/PopupMenu.java
src/jalview/gui/StructureChooser.java
src/jalview/gui/TreePanel.java
src/jalview/jbgui/GAlignFrame.java
src/jalview/project/Jalview2XML.java
src/jalview/renderer/AnnotationRenderer.java
src/jalview/renderer/ResidueShader.java
src/jalview/renderer/ResidueShaderI.java
src/jalview/viewmodel/AlignmentViewport.java
src/jalview/workers/SecondaryStructureConsensusThread.java
test/jalview/commands/EditCommandTest.java

index f56b91d..6df3ec9 100644 (file)
@@ -1769,7 +1769,7 @@ task testTask0(type: Test) {
     preserveOrder true
     useDefaultListeners=true
   }
-  timeout = Duration.ofMinutes(10)
+  timeout = Duration.ofMinutes(15)
 }
 
 /* separated tests */
index 7384cda..a5bc366 100755 (executable)
         one of the sequences (the shorter) is discarded. Press the
         &quot;Apply&quot; button to remove redundant sequences. The
         &quot;Undo&quot; button will undo the last redundancy deletion.</em></li>
+    <li><strong>Left Justify</strong> and <strong>Right
+        Justify<br>
+    </strong> <em>Moves all gaps to either right or left of the sequences in
+        the alignment or the currently selected region.</em></li>
     <li><strong>Pad Gaps<br>
     </strong><em>When selected, the alignment will be kept at minimal width
         (so there are no empty columns before or after the first or last
index dcbe8e8..8a91ecf 100644 (file)
@@ -1,15 +1,21 @@
 ---
 version: 2.11.3.3
-date: 2024-01-24
+date: 2024-05-16
 channel: "release"
 ---
 
 ## New Features
 
+- <!-- JAL-518 --> Left/Right justify the alignment or current selection (with Undo)
 - <!-- JAL-4192 --> Minimum versions of Jalview's build toolchain are explicitly specified in doc/building.md
 
+### development and deployment
+
+- <!-- JAL-4413 --> timeout for test tasks to prevent hanging builds
+
 ## Issues Resolved
 
+- <!-- JAL-4407 --> Secondary structure not available after dragging a 3D structure onto a sequence to associate it when 'Process secondary structure' flag is true in preferences
 - <!-- JAL-840 --> Clustal colourscheme documentation not consistent with implementation
 - <!-- JAL-4367 --> Handles to adjust ID panel width or annotation panel height can be dragged out of the Alignment window
 - <!-- JAL-2934 --> Trackpad scrolling not proportional to gesture velocity
@@ -20,3 +26,9 @@ channel: "release"
 - <!-- JAL-4369 --> Uniprot record retrieval not working in JalviewJS (affects all versions)
 - <!-- JAL-4243 --> bio.tools record version number updated
 - <!-- JAL-4217 --> Miscellaneous patches to fix hangs in 2.11.3 test suite
+
+### New Known Issues
+
+- <!-- JAL-3124 --> Protein structure derived annotation tracks not available as reference annotation unless 'add to alignment' preference is enabled
+- <!-- JAL-4415 --> Enabling just "Add Secondary Structure" Structure preference when importing structure via CLI results in temperature factor being shown in alignment
+
index de5e441..5dbeb43 100644 (file)
@@ -1 +1 @@
-The 2.11.3.3 release features minor updates to documentation, and addresses bugs affecting export of multiple images for structure views.
+The 2.11.3.3 release features minor updates to documentation, and addresses bugs affecting export of multiple images for structure views, trackpad scrolling, sequence ID margin width, and extraction of secondary structure for imported models. It also includes two new alignment editing operations - Left and Right justify, which can be applied to the whole alignment or current selection. 
index 9e68eab..cd76e14 100644 (file)
@@ -66,8 +66,10 @@ action.remove_left = Remove left
 action.remove_right = Remove right
 action.remove_empty_columns = Remove Empty Columns
 action.remove_all_gaps = Remove All Gaps
-action.left_justify_alignment = Left Justify Alignment
-action.right_justify_alignment = Right Justify Alignment
+tooltip.left_justify = Left justify whole alignment or selected region
+tooltip.right_justify = Right justify whole alignment or selected region
+action.left_justify = Left Justify
+action.right_justify = Right Justify
 action.boxes = Boxes
 action.text = Text
 action.by_pairwise_id = By Pairwise Identity
@@ -1293,7 +1295,9 @@ label.quality_descr = Alignment Quality based on Blosum62 scores
 label.conservation_descr = Conservation of total alignment less than {0}% gaps
 label.consensus_descr = PID
 label.ssconsensus_label = Secondary Structure Consensus
-label.ssconsensus_descr = SS Consensus
+label.ssconsensus_descr = Secondary Structure Consensus
+option.ss_providers_all = All
+option.ss_providers_none = None
 label.complement_consensus_descr = PID for cDNA
 label.strucconsensus_descr = PID for base pairs
 label.occupancy_descr = Number of aligned positions 
index 707110a..a979c45 100755 (executable)
@@ -196,13 +196,13 @@ public class AAFrequency
 
   
   public static final ProfilesI calculateSS(List<SequenceI> list, int start,
-          int end)
+          int end, String source)
   {
-    return calculateSS(list, start, end, false);
+    return calculateSS(list, start, end, false, source);
   }
   
   public static final ProfilesI calculateSS(List<SequenceI> sequences,
-          int start, int end, boolean profile)
+          int start, int end, boolean profile, String source)
   {
     SequenceI[] seqs = new SequenceI[sequences.size()];
     int width = 0;
@@ -222,14 +222,15 @@ public class AAFrequency
       {
         end = width;
       }
+      
 
-      ProfilesI reply = calculateSS(seqs, width, start, end, profile);
+      ProfilesI reply = calculateSS(seqs, width, start, end, profile, source);
       return reply;
     }
   }
   
   public static final ProfilesI calculateSS(final SequenceI[] sequences,
-          int width, int start, int end, boolean saveFullProfile)
+          int width, int start, int end, boolean saveFullProfile, String source)
   {
 
     int seqCount = sequences.length;
@@ -253,27 +254,31 @@ public class AAFrequency
         }
         
         char c = sequences[row].getCharAt(column);
-        AlignmentAnnotation aa = AlignmentUtils.getDisplayedAlignmentAnnotation(sequences[row]);
-        if(aa!=null) {
-          ssCount++;
-        }
-        
-        if (sequences[row].getLength() > column && !Comparison.isGap(c) && aa !=null)
-        {
-          
-          int seqPosition = sequences[row].findPosition(column);
+        List<AlignmentAnnotation> annots = AlignmentUtils.getAlignmentAnnotationForSource(sequences[row], source);
+        if(annots!=null) {
+        for(AlignmentAnnotation aa:annots) {
+          if(aa!=null) {
+            ssCount++;
+          }
           
-          char ss = AlignmentUtils.findSSAnnotationForGivenSeqposition(
-                  aa, seqPosition); 
-          if(ss == '*') {
-            continue;
-          }        
-          ssCounts.add(ss);                    
-        }
-        else if(Comparison.isGap(c) && aa!=null) {
-          ssCounts.addGap();
+          if (sequences[row].getLength() > column && !Comparison.isGap(c) && aa !=null)
+          {
+            
+            int seqPosition = sequences[row].findPosition(column);
+            
+            char ss = AlignmentUtils.findSSAnnotationForGivenSeqposition(
+                    aa, seqPosition); 
+            if(ss == '*') {
+              continue;
+            }        
+            ssCounts.add(ss);                    
+          }
+          else if(Comparison.isGap(c) && aa!=null) {
+            ssCounts.addGap();
+          }
         }
       }
+      }
 
       int maxSSCount = ssCounts.getModalCount();
       String maxSS = ssCounts.getSSForCount(maxSSCount);
index 7d0ccdb..7da3ee0 100644 (file)
@@ -70,6 +70,7 @@ import jalview.util.DBRefUtils;
 import jalview.util.IntRangeComparator;
 import jalview.util.MapList;
 import jalview.util.MappingUtils;
+import jalview.util.MessageManager;
 import jalview.workers.SecondaryStructureConsensusThread;
 
 /**
@@ -2854,193 +2855,302 @@ public class AlignmentUtils
   }
   
 
-  public static List<String> getSecondaryStructureSources(AlignmentAnnotation[] annotations) {
-    
-      List<String> ssSources = new ArrayList<>();
-      Set<String> addedLabels = new HashSet<>(); // to keep track of added labels
-
-      for (AlignmentAnnotation annotation : annotations) {
-          String label = annotation.label;
-          if (Constants.SECONDARY_STRUCTURE_LABELS.containsKey(label) && !addedLabels.contains(label)) {
-              ssSources.add(Constants.SECONDARY_STRUCTURE_LABELS.get(label));
-              addedLabels.add(label); // Add the label to the set
-          }
+  public static List<String> getSecondaryStructureSources(
+          AlignmentAnnotation[] annotations)
+  {
+
+    List<String> ssSources = new ArrayList<>();
+    Set<String> addedLabels = new HashSet<>(); // to keep track of added labels
+
+    for (AlignmentAnnotation annotation : annotations)
+    {
+      String label = annotation.label;
+      if (Constants.SECONDARY_STRUCTURE_LABELS.containsKey(label)
+              && !addedLabels.contains(label))
+      {
+        ssSources.add(Constants.SECONDARY_STRUCTURE_LABELS.get(label));
+        addedLabels.add(label); // Add the label to the set
       }
+    }
 
-      return ssSources;
+    return ssSources;
   }
   
   public static boolean isSecondaryStructurePresent(AlignmentAnnotation[] annotations)
   {
     boolean ssPresent = false;
-    
+
     for (AlignmentAnnotation aa : annotations)
     {
-      if(ssPresent) {
+      if (ssPresent)
+      {
         break;
-      }     
+      }
 
-      if (Constants.SECONDARY_STRUCTURE_LABELS.containsKey(aa.label)) {
-          ssPresent = true;
-          break;
+      if (Constants.SECONDARY_STRUCTURE_LABELS.containsKey(aa.label))
+      {
+        ssPresent = true;
+        break;
       }
     }
-    
+
     return ssPresent;
-    
+
   }
   
-  public static Color getSecondaryStructureAnnotationColour(char symbol){
-   
-    if (symbol== Constants.COIL) {
+  public static Color getSecondaryStructureAnnotationColour(char symbol)
+  {
+
+    if (symbol == Constants.COIL)
+    {
       return Color.gray;
     }
-    if (symbol== Constants.SHEET) {
+    if (symbol == Constants.SHEET)
+    {
       return Color.green;
     }
-    if (symbol== Constants.HELIX) {
+    if (symbol == Constants.HELIX)
+    {
       return Color.red;
     }
-    
+
     return Color.gray;
   }
 
   public static char findSSAnnotationForGivenSeqposition(AlignmentAnnotation aa,
           int seqPosition)
   {
-    char ss = '*'; 
-        
-    if (aa != null) {
-      if (aa.getAnnotationForPosition(seqPosition) != null) {
+    char ss = '*';
+
+    if (aa != null)
+    {
+      if (aa.getAnnotationForPosition(seqPosition) != null)
+      {
         Annotation a = aa.getAnnotationForPosition(seqPosition);
         ss = a.secondaryStructure;
-        
-        //There is no representation for coil and it can be either ' ' or null. 
-        if (ss == ' ' || ss == '-') {
-          ss = Constants.COIL; 
+
+        // There is no representation for coil and it can be either ' ' or null.
+        if (ss == ' ' || ss == '-')
+        {
+          ss = Constants.COIL;
         }
       }
-      else {
+      else
+      {
         ss = Constants.COIL;
-      }                 
+      }
     }
-    
-    return ss;    
+
+    return ss;
   }
   
  
-  public static List<String> extractSSSourceInAlignmentAnnotation(AlignmentAnnotation[] annotations) {
-    
+  public static List<String> extractSSSourceInAlignmentAnnotation(
+          AlignmentAnnotation[] annotations)
+  {
+
     List<String> ssSources = new ArrayList<>();
-    Set<String> addedSources = new HashSet<>(); // to keep track of added sources
+    Set<String> addedSources = new HashSet<>(); // to keep track of added
+                                                // sources
+
+    if (annotations == null)
+    {
+      return ssSources;
+    }
+
+    for (AlignmentAnnotation aa : annotations)
+    {
 
-          
-    for (AlignmentAnnotation aa: annotations) {
-      
       String ssSource = extractSSSourceFromAnnotationDescription(aa);
-      
-      if (ssSource!= null && !addedSources.contains(ssSource)) {
-          ssSources.add(ssSource);
-          addedSources.add(ssSource);
+
+      if (ssSource != null && !addedSources.contains(ssSource))
+      {
+        ssSources.add(ssSource);
+        addedSources.add(ssSource);
       }
-      
-     }
+
+    }
     Collections.sort(ssSources);
-       
+
     return ssSources;
-    
+
   }
   
-  public static String extractSSSourceFromAnnotationDescription(AlignmentAnnotation aa) {
-    
-    
-    for (String label : Constants.SECONDARY_STRUCTURE_LABELS.keySet()) {
-      
-      if (label.equals(aa.label)) {  
-        
-        //For JPred 
-        if(aa.label.equals(Constants.SS_ANNOTATION_FROM_JPRED_LABEL)){
-          
+  public static String extractSSSourceFromAnnotationDescription(
+          AlignmentAnnotation aa)
+  {
+
+    for (String label : Constants.SECONDARY_STRUCTURE_LABELS.keySet())
+    {
+
+      if (label.equals(aa.label))
+      {
+
+        // For JPred
+        if (aa.label.equals(Constants.SS_ANNOTATION_FROM_JPRED_LABEL))
+        {
+
           return (Constants.SECONDARY_STRUCTURE_LABELS.get(aa.label));
-          
-         }
-        
-        //For input with secondary structure
-        if(aa.label.equals(Constants.SS_ANNOTATION_LABEL) 
-                && aa.description.equals(Constants.SS_ANNOTATION_LABEL)){
-          
+
+        }
+
+        // For input with secondary structure
+        if (aa.label.equals(Constants.SS_ANNOTATION_LABEL)
+                && aa.description != null && aa.description.equals(Constants.SS_ANNOTATION_LABEL))
+        {
+
           return (Constants.SECONDARY_STRUCTURE_LABELS.get(aa.label));
-          
-         }
-        
-        //For other sources
-        if(aa.sequenceRef==null) {
+
+        }
+
+        // For other sources
+        if (aa.sequenceRef == null)
+        {
           return null;
         }
-        else if(aa.sequenceRef.getDatasetSequence()==null) {
+        else if (aa.sequenceRef.getDatasetSequence() == null)
+        {
           return null;
         }
-         Vector<PDBEntry> pdbEntries = aa.sequenceRef.getDatasetSequence().getAllPDBEntries();
-         
-         for (PDBEntry entry : pdbEntries){
-           
-           String entryProvider = entry.getProvider();
-           if(entryProvider == null) {
-             entryProvider = "PDB";
-           }
-           
-           // Trim the string from first occurrence of colon
-           String entryID = entry.getId();
-           int index = entryID.indexOf(':');
-           
-           // Check if colon exists
-           if (index != -1) {
-             
-             // Trim the string from first occurrence of colon
-             entryID = entryID.substring(0, index); 
-             
-           }
-           
-           if(entryProvider == "PDB" && aa.description.toLowerCase().contains
-                     ("secondary structure for " + entryID.toLowerCase())){
-               
-               return entryProvider;
-             
-           }
-           
-           else if (entryProvider != "PDB" && aa.description.toLowerCase().contains(entryID.toLowerCase())){
-              
-              return entryProvider;
-            
-            }
-          
+        Vector<PDBEntry> pdbEntries = aa.sequenceRef.getDatasetSequence()
+                .getAllPDBEntries();
+
+        for (PDBEntry entry : pdbEntries)
+        {
+
+          String entryProvider = entry.getProvider();
+          if (entryProvider == null)
+          {
+            entryProvider = "PDB";
+          }
+
+          // Trim the string from first occurrence of colon
+          String entryID = entry.getId();
+          int index = entryID.indexOf(':');
+
+          // Check if colon exists
+          if (index != -1)
+          {
+
+            // Trim the string from first occurrence of colon
+            entryID = entryID.substring(0, index);
+
+          }
+
+          if (entryProvider == "PDB" && aa.description.toLowerCase()
+                  .contains("secondary structure for "
+                          + entryID.toLowerCase()))
+          {
+
+            return entryProvider;
+
+          }
+
+          else if (entryProvider != "PDB" && aa.description.toLowerCase()
+                  .contains(entryID.toLowerCase()))
+          {
+
+            return entryProvider;
+
           }
-          
-        }   
+
+        }
       }
-    
+    }
+
     return null;
-    
+
   }
   
   //to do set priority for labels
-  public static AlignmentAnnotation getDisplayedAlignmentAnnotation(SequenceI seq){
-    
-    for(String ssLabel : Constants.SECONDARY_STRUCTURE_LABELS.keySet()) {
-    
-    AlignmentAnnotation[] aa = seq.getAnnotation(ssLabel);
-    if(aa!=null) {
-      
-      for (AlignmentAnnotation annot: aa) {
-        if(annot.visible) {
-          return annot;
+  public static List<AlignmentAnnotation> getAlignmentAnnotationForSource(
+          SequenceI seq, String ssSource)
+  {
+
+    List<AlignmentAnnotation> ssAnnots = new ArrayList<AlignmentAnnotation>();
+    for (String ssLabel : Constants.SECONDARY_STRUCTURE_LABELS.keySet())
+    {
+
+      AlignmentAnnotation[] aa = seq.getAnnotation(ssLabel);
+      if (aa != null)
+      {
+
+        if (ssSource.equals(MessageManager.getString("option.ss_providers_all")))
+        {
+          ssAnnots.addAll(Arrays.asList(aa));
+          continue;
+        }
+
+        for (AlignmentAnnotation annot : aa)
+        {
+
+          if (ssSource
+                  .equals(extractSSSourceFromAnnotationDescription(annot)))
+          {
+            ssAnnots.add(annot);
+          }
         }
       }
-     }
     }
-    
+    if (ssAnnots.size() > 0)
+    {
+      return ssAnnots;
+    }
+
     return null;
-    
+
+  }
+
+  public static Map<SequenceI, ArrayList<AlignmentAnnotation>> getSequenceAssociatedAlignmentAnnotations(
+          AlignmentAnnotation[] alignAnnotList, String selectedSSSource)
+  {
+
+    Map<SequenceI, ArrayList<AlignmentAnnotation>> ssAlignmentAnnotationForSequences 
+             = new HashMap<SequenceI, ArrayList<AlignmentAnnotation>>();
+    if (alignAnnotList == null || alignAnnotList.length == 0)
+    {
+      return ssAlignmentAnnotationForSequences;
+    }
+
+    for (AlignmentAnnotation aa : alignAnnotList)
+    {
+      if (aa.sequenceRef == null)
+      {
+        continue;
+      }
+      
+      for (String label : Constants.SECONDARY_STRUCTURE_LABELS.keySet())
+      {
+
+        if (label.equals(aa.label))
+        {
+
+          if (selectedSSSource.equals(MessageManager.getString("option.ss_providers_all")))
+          {
+            ssAlignmentAnnotationForSequences
+                    .computeIfAbsent(aa.sequenceRef.getDatasetSequence(),
+                            k -> new ArrayList<>())
+                    .add(aa);
+            break;
+          }
+
+          String ssSource = AlignmentUtils
+                  .extractSSSourceFromAnnotationDescription(aa);
+          if (ssSource.equals(selectedSSSource))
+          {
+
+            ssAlignmentAnnotationForSequences
+                    .computeIfAbsent(aa.sequenceRef.getDatasetSequence(),
+                            k -> new ArrayList<>())
+                    .add(aa);
+            break;
+          }
+        }
+      }
+    }
+
+    return ssAlignmentAnnotationForSequences;
+
   }
   
 }
index 6cce5b4..4e3a3de 100644 (file)
@@ -27,14 +27,13 @@ import jalview.api.analysis.ScoreModelI;
 import jalview.api.analysis.SimilarityParamsI;
 import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.AlignmentView;
-import jalview.datamodel.Annotation;
 import jalview.datamodel.SeqCigar;
+import jalview.datamodel.SequenceI;
 import jalview.math.Matrix;
 import jalview.math.MatrixI;
-import jalview.util.Constants;
-import jalview.util.SetUtils;
+import jalview.util.MessageManager;
 
-import java.util.HashMap;
+import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
@@ -110,53 +109,29 @@ public class SecondaryStructureDistanceModel extends DistanceScoreModel
     
     SeqCigar[] seqs = seqData.getSequences();
     int noseqs = seqs.length; //no of sequences
-    int cpwidth = 0; // = seqData.getWidth();
+    int cpwidth = 0; 
     double[][] similarities = new double[noseqs][noseqs]; //matrix to store similarity score
     //secondary structure source parameter selected by the user from the drop down.
     String ssSource = params.getSecondaryStructureSource(); 
-    ssRateMatrix = ScoreModels.getInstance().getSecondaryStructureMatrix();
-    
-    //defining the default value for secondary structure source as 3d structures 
-    //or JPred if user selected JPred
-    String selectedSSSource = Constants.SS_ANNOTATION_LABEL;
-    if(ssSource.equals(Constants.SECONDARY_STRUCTURE_LABELS.get(Constants.SS_ANNOTATION_FROM_JPRED_LABEL)))
-    {
-      selectedSSSource = Constants.SS_ANNOTATION_FROM_JPRED_LABEL;
+    if(ssSource == null || ssSource == "") {
+      ssSource = MessageManager.getString("option.ss_providers_all");
     }
+    ssRateMatrix = ScoreModels.getInstance().getSecondaryStructureMatrix();
         
     // need to get real position for view position
     int[] viscont = seqData.getVisibleContigs();
     
-    /*
-     * Add secondary structure annotations that are added to the annotation track
-     * to the map
-     */
-    Map<String, HashSet<String>> ssAlignmentAnnotationForSequences 
-      = new HashMap<String,HashSet<String>>();    
     
     AlignmentAnnotation[] alignAnnotList = fr.getViewport().getAlignment()
             .getAlignmentAnnotation();   
     
-    if(alignAnnotList.length > 0) {      
-      for (AlignmentAnnotation aa: alignAnnotList) {
-        if (selectedSSSource.equals(aa.label)) {          
-          ssAlignmentAnnotationForSequences.computeIfAbsent(
-                  aa.sequenceRef.getName(), k -> new HashSet<>())
-          .add(aa.description);
-        }        
-      }      
-    }
-    
+
     /*
-     * Get the set of sequences which are not considered for the calculation.
-     * Following sequences are added:
-     * 1. Sequences without a defined secondary structure from the selected 
-     * source.
-     * 2. Sequences whose secondary structure annotations are not added to 
-     * the annotation track
+     * Add secondary structure annotations that are added to the annotation track
+     * to the map
      */
-    Set<SeqCigar> seqsWithUndefinedSS 
-        = findSeqsWithUndefinedSS(seqs, ssAlignmentAnnotationForSequences);
+    Map<SequenceI, ArrayList<AlignmentAnnotation>> ssAlignmentAnnotationForSequences 
+      = AlignmentUtils.getSequenceAssociatedAlignmentAnnotations(alignAnnotList, ssSource); 
 
     /*
      * scan each column, compute and add to each similarity[i, j]
@@ -185,13 +160,10 @@ public class SecondaryStructureDistanceModel extends DistanceScoreModel
           //Iterates for each sequences
           for (int j = i + 1; j < noseqs; j++)
           {
-            SeqCigar sc1 = seqs[i];
-            SeqCigar sc2 = seqs[j];
                          
-
             //check if ss is defined
-            boolean undefinedSS1 = seqsWithUndefinedSS.contains(sc1);
-            boolean undefinedSS2 = seqsWithUndefinedSS.contains(sc2);
+            boolean undefinedSS1 = ssAlignmentAnnotationForSequences.get(seqs[i].getRefSeq()) == null;
+            boolean undefinedSS2 = ssAlignmentAnnotationForSequences.get(seqs[j].getRefSeq()) == null;
 
             // Set similarity to max score if both SS are not defined
             if (undefinedSS1 && undefinedSS2) {
@@ -206,8 +178,8 @@ public class SecondaryStructureDistanceModel extends DistanceScoreModel
             }
             
             //check if the sequence contains gap in the current column
-            boolean gap1 = !seqsWithoutGapAtCol.contains(sc1);
-            boolean gap2 = !seqsWithoutGapAtCol.contains(sc2);            
+            boolean gap1 = !seqsWithoutGapAtCol.contains(seqs[i]);
+            boolean gap2 = !seqsWithoutGapAtCol.contains(seqs[j]);            
             
             //Variable to store secondary structure at the current column
             char ss1 = '*';
@@ -220,18 +192,18 @@ public class SecondaryStructureDistanceModel extends DistanceScoreModel
               //corresponding secondary structure annotation
               //TO DO - consider based on priority and displayed
               int seqPosition = seqs[i].findPosition(cpos);
-              AlignmentAnnotation[] aa = seqs[i].getRefSeq().getAnnotation(selectedSSSource);
+              AlignmentAnnotation aa = ssAlignmentAnnotationForSequences.get(seqs[i].getRefSeq()).get(0);
               if(aa!=null)
               ss1 = 
-                  AlignmentUtils.findSSAnnotationForGivenSeqposition(aa[0], seqPosition);              
+                  AlignmentUtils.findSSAnnotationForGivenSeqposition(aa, seqPosition);              
             }
             
             if(!gap2 && !undefinedSS2) {              
               int seqPosition = seqs[j].findPosition(cpos);
-              AlignmentAnnotation[] aa = seqs[j].getRefSeq().getAnnotation(selectedSSSource);
+              AlignmentAnnotation aa = ssAlignmentAnnotationForSequences.get(seqs[j].getRefSeq()).get(0);
               if(aa!=null)
                 ss2 = 
-                  AlignmentUtils.findSSAnnotationForGivenSeqposition(aa[0], seqPosition);               
+                  AlignmentUtils.findSSAnnotationForGivenSeqposition(aa, seqPosition);               
             }           
 
             if ((!gap1 && !gap2) || params.includeGaps())
@@ -291,65 +263,6 @@ public class SecondaryStructureDistanceModel extends DistanceScoreModel
     return seqsWithoutGapAtCol;
   }
 
-  
-  /**
-   * Builds and returns a set containing sequences (SeqCigar) which
-   * are not considered for the similarity calculation.
-   * Following sequences are added:
-   * 1. Sequences without a defined secondary structure from the selected 
-   * source.
-   * 2. Sequences whose secondary structure annotations are not added to 
-   * the annotation track
-   * @param seqs
-   * @param ssAlignmentAnnotationForSequences         
-   * @return
-   */
-  private Set<SeqCigar> findSeqsWithUndefinedSS(SeqCigar[] seqs,
-          Map<String, HashSet<String>> ssAlignmentAnnotationForSequences) {
-      Set<SeqCigar> seqsWithUndefinedSS = new HashSet<>();
-      for (SeqCigar seq : seqs) {
-          if (isSSUndefinedOrNotAdded(seq, ssAlignmentAnnotationForSequences)) {
-              seqsWithUndefinedSS.add(seq);
-          }
-      }
-      return seqsWithUndefinedSS;
-  }
-  
-  
-  /**
-   * Returns true if a sequence (SeqCigar) should not be
-   * considered for the similarity calculation.
-   * Following conditions are checked:
-   * 1. Sequence without a defined secondary structure from the selected 
-   * source.
-   * 2. Sequences whose secondary structure annotations are not added to 
-   * the annotation track
-   * @param seq
-   * @param ssAlignmentAnnotationForSequences
-   * @return
-   */
-  private boolean isSSUndefinedOrNotAdded(SeqCigar seq, 
-          Map<String, HashSet<String>> ssAlignmentAnnotationForSequences) {
-      for (String label : Constants.SECONDARY_STRUCTURE_LABELS.keySet()) {
-          AlignmentAnnotation[] annotations = seq.getRefSeq().getAnnotation(label);
-          if (annotations != null) {
-              for (AlignmentAnnotation annotation : annotations) {                
-                HashSet<String> descriptionSet = ssAlignmentAnnotationForSequences
-                        .get(annotation.sequenceRef.getName());
-                if (descriptionSet != null)
-                {
-                  if (descriptionSet.contains(annotation.description)) {
-                      // Secondary structure annotation is present and 
-                      //added to the track, no need to add seq
-                      return false;
-                  }
-                }
-              }
-          }
-      }
-      // Either annotations are undefined or not added to the track
-      return true;
-  }
     
   @Override
   public String getName()
index 3e689d1..25e4873 100644 (file)
@@ -119,4 +119,10 @@ public interface AlignViewControllerI
    */
   boolean copyHighlightedRegionsToClipboard();
 
+  /**
+   * Justify alignment or currently selected region left or right
+   * @param left - true - means justify left
+   * @return
+   */
+  boolean justify_Region(boolean left);
 }
index b7747f5..126c8c1 100644 (file)
  */
 package jalview.api;
 
+import java.awt.Color;
+import java.awt.Font;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
 import jalview.analysis.Conservation;
 import jalview.analysis.TreeModel;
 import jalview.datamodel.AlignmentAnnotation;
@@ -38,13 +45,6 @@ import jalview.renderer.ResidueShaderI;
 import jalview.schemes.ColourSchemeI;
 import jalview.viewmodel.ViewportRanges;
 
-import java.awt.Color;
-import java.awt.Font;
-import java.util.Hashtable;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-
 /**
  * @author jimp
  * 
@@ -134,7 +134,7 @@ public interface AlignViewportI extends ViewStyleI
    */
   AlignmentAnnotation getAlignmentConsensusAnnotation();
   
-  AlignmentAnnotation getAlignmentSecondaryStructureConsensusAnnotation();
+  List<AlignmentAnnotation> getAlignmentSecondaryStructureConsensusAnnotation();
 
 
   /**
@@ -183,7 +183,7 @@ public interface AlignViewportI extends ViewStyleI
    */
   void setSequenceConsensusHash(ProfilesI hconsensus);
   
-  void setSequenceSSConsensusHash(ProfilesI hSSConsensus);
+  void setSequenceSSConsensusHash(Map<String, ProfilesI> hSSConsesnusProfileMap);
   
 
   /**
@@ -575,9 +575,23 @@ public interface AlignViewportI extends ViewStyleI
    * @return
    */
   Iterator<int[]> getViewAsVisibleContigs(boolean selectedRegionOnly);
+  /**
+   * notify all concerned that the alignment data has changed and derived data
+   * needs to be recalculated
+   */
+  public void notifyAlignmentChanged();
 
+  /**
+   * retrieve a matrix associated with the view's alignment's annotation 
+   * @param alignmentAnnotation
+   * @return contact matrix or NULL
+   */
   ContactMatrixI getContactMatrix(AlignmentAnnotation alignmentAnnotation);
 
-  ProfilesI getSequenceSSConsensusHash();
+  Map<String, ProfilesI> getSequenceSSConsensusHash();
+
+  List<String> getSecondaryStructureSources();
+
+  void setSecondaryStructureSources(List<String> secondaryStructureSources);
 
 }
diff --git a/src/jalview/commands/JustifyLeftOrRightCommand.java b/src/jalview/commands/JustifyLeftOrRightCommand.java
new file mode 100644 (file)
index 0000000..2559662
--- /dev/null
@@ -0,0 +1,98 @@
+/*
+ * 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.commands;
+
+import java.util.List;
+
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.ContiguousI;
+import jalview.datamodel.SequenceI;
+import jalview.util.Comparison;
+
+public class JustifyLeftOrRightCommand extends EditCommand
+{
+  /**
+   * Constructs and performs a trim alignment command
+   * 
+   * @param description
+   *          (to show in Undo/Redo menu)
+   * @param justifyLeft
+   *          if true left justify, otherwise right
+   * @param seqs
+   *          the sequences to justify
+   * @param start
+   *          - leftmost column (base 0) to justify
+   * 
+   * @param end
+   *          - rightmost column (base 0) to justify
+   * 
+   * @param al
+   */
+  public JustifyLeftOrRightCommand(String description, boolean left,
+          List<SequenceI> seqs, int from, int to, AlignmentI al)
+  {
+    this.description = description;
+
+    for (SequenceI seq : seqs)
+    {
+      ContiguousI cont = seq.findPositions(from + 1, to + 1);
+      if (cont == null)
+      {
+        continue;
+      }
+      char[] range = seq.getSequence(from, to+1);
+      if (range==null || range.length==0)
+      {
+        continue;
+      }
+      int dsstart = seq.getDatasetSequence().getStart();
+      char[] sqchar = seq.getDatasetSequence().getSequence(
+              -dsstart + cont.getBegin(), -dsstart + cont.getEnd() + 1);
+      // debug
+      // println sqchar;
+      char[] alseq = new char[to - from + 1];
+      int sqstart = left ? 0 : alseq.length - sqchar.length;
+      int gaps = alseq.length - sqchar.length;
+      int gapstart = left ? sqchar.length : 0;
+      char gc = al.getGapCharacter();
+      for (int gp = 0; gp < gaps; gp++)
+      {
+        alseq[gapstart + gp] = gc;
+      }
+
+      for (int sqp = 0,insp=0; sqp<alseq.length; sqp++)
+      {
+        if (sqp < range.length && !Comparison.isGap(range[sqp]))
+        {
+          alseq[insp++ + sqstart] = range[sqp];
+        }
+      }
+      SequenceI[] sqa = new SequenceI[1];
+      sqa[0] = seq;
+
+      addEdit(new jalview.commands.EditCommand.Edit(
+              jalview.commands.EditCommand.Action.REPLACE, sqa, from,
+              to + 1, al, new String(alseq)));
+    }
+
+    performEdit(0, null);
+  }
+}
index 4434331..bccacfa 100644 (file)
@@ -500,4 +500,36 @@ public class AlignViewController implements AlignViewControllerI
     // Technically we should return false, since view has not changed
     return false;
   }
+  
+  @Override
+  public boolean justify_Region(boolean left)
+  {
+    AlignmentI al = viewport.getAlignment();
+    SequenceGroup reg = viewport.getSelectionGroup();
+    int from, to;
+    List<SequenceI> seqs;
+
+    from = 0;
+    to = al.getWidth() - 1;
+    seqs = al.getSequences();
+    if (reg != null)
+    {
+      seqs = reg.getSequences();
+      from = reg.getStartRes();
+      to = reg.getEndRes();
+    }
+
+    if ((to - from) < 1)
+    {
+      return false;
+    }
+
+    al.padGaps();
+    jalview.commands.JustifyLeftOrRightCommand finalEdit = new jalview.commands.JustifyLeftOrRightCommand(
+            "Justify " + (left ? "Left" : "Right"), left, seqs, from, to,
+            al);
+    avcg.addHistoryItem(finalEdit);
+    viewport.notifyAlignmentChanged();
+    return true;
+  }
 }
index 73ccdc5..ce9250c 100755 (executable)
@@ -71,6 +71,8 @@ public class Alignment implements AlignmentI, AutoCloseable
   public Hashtable alignmentProperties;
 
   private List<AlignedCodonFrame> codonFrameList;
+  
+  private List<String> secondaryStructureSources;
 
   private void initAlignment(SequenceI[] seqs)
   {
@@ -82,12 +84,13 @@ public class Alignment implements AlignmentI, AutoCloseable
     nucleotide = Comparison.isNucleotide(seqs);
 
     sequences = Collections.synchronizedList(new ArrayList<SequenceI>());
+    
 
     for (int i = 0; i < seqs.length; i++)
     {
       sequences.add(seqs[i]);
     }
-
+   
   }
 
   /**
@@ -2122,4 +2125,16 @@ public class Alignment implements AlignmentI, AutoCloseable
     cmholder.addContactListFor(annotation, cm);
 
   }
+
+  public List<String> getSecondaryStructureSources()
+  {
+    return secondaryStructureSources;
+  }
+
+  public void setSecondaryStructureSources(
+          List<String> secondaryStructureSources)
+  {
+    this.secondaryStructureSources = secondaryStructureSources;
+  }
+
 }
index b5048a0..5416ac4 100755 (executable)
@@ -26,6 +26,8 @@ import java.beans.PropertyChangeSupport;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
@@ -34,6 +36,7 @@ import jalview.analysis.Conservation;
 import jalview.renderer.ResidueShader;
 import jalview.renderer.ResidueShaderI;
 import jalview.schemes.ColourSchemeI;
+import jalview.util.MessageManager;
 
 /**
  * Collects a set contiguous ranges on a set of sequences
@@ -154,7 +157,7 @@ public class SequenceGroup implements AnnotatedCollectionI
   AlignmentAnnotation consensus = null;
   
 
-  AlignmentAnnotation ssConsensus = null;
+  List<AlignmentAnnotation> ssConsensus = null;
 
   AlignmentAnnotation conservation = null;
 
@@ -619,18 +622,31 @@ public class SequenceGroup implements AnnotatedCollectionI
         cs.setConsensus(cnsns);
         upd = true;
       }
+
+      Map<String, ProfilesI> hSSConsensusProfileMap = new HashMap<String, ProfilesI>();
+      List <String> ssSources = new ArrayList<String>();
       
-      
-      ProfilesI ssCnsns = AAFrequency.calculateSS(sequences, startRes,
-              endRes + 1, showSequenceLogo);
+        //ssSources = AlignmentUtils.extractSSSourceInAlignmentAnnotation(ssConsensus.toArray());
+      if(ssSources != null) {
+        ssSources.add(MessageManager.getString("option.ss_providers_all"));
+
+        for(String ssSource : ssSources) {
+          ProfilesI hSSConsensus = AAFrequency.calculateSS(sequences, startRes,  endRes + 1, showSequenceLogo,
+                  ssSource);
+          hSSConsensusProfileMap.put(ssSource, hSSConsensus);
+        }
+      }
+            
+
       if (ssConsensus != null)
       {
-        _updateSSConsensusRow(ssCnsns, sequences.size());
+        _updateSSConsensusRow(hSSConsensusProfileMap, sequences.size());
         upd = true;
       }
+      
       if (cs != null)
       {
-        cs.setSsConsensus(ssCnsns);
+        cs.setSSConsensusProfileMap(hSSConsensusProfileMap);
         upd = true;
       }
       
@@ -726,27 +742,44 @@ public class SequenceGroup implements AnnotatedCollectionI
   
   public ProfilesI ssConsensusData = null;
   
-  private void _updateSSConsensusRow(ProfilesI ssCnsns, long nseq)
+  private void _updateSSConsensusRow(Map<String, ProfilesI> hSSConsensusProfileMap, long nseq)
   {
+    List<String> ssSources = new ArrayList<>(hSSConsensusProfileMap.keySet());
+
+    Collections.sort(ssSources);
     if (ssConsensus == null)
     {
-      getSSConsensus();
+      getSSConsensus(ssSources);
     }
-    ssConsensus.label = "Sec Str Consensus for " + getName();
-    ssConsensus.description = "Percent Identity";
-    ssConsensusData = ssCnsns;
-    // preserve width if already set
-    int aWidth = (ssConsensus.annotations != null)
-            ? (endRes < ssConsensus.annotations.length
-                    ? ssConsensus.annotations.length
-                    : endRes + 1)
-            : endRes + 1;
-    ssConsensus.annotations = null;
-    ssConsensus.annotations = new Annotation[aWidth]; // should be alignment width
+    for (AlignmentAnnotation aa : ssConsensus) {
+      ProfilesI profile = null;
+      String ssSource = null;
+      for(String source : ssSources) {
+        if(aa.description.startsWith(source)) {
+          profile = hSSConsensusProfileMap.get(source);
+          ssSource = source;
+        }
+      }
+      if(profile == null) {
+        continue;
+      }
 
-    AAFrequency.completeSSConsensus(ssConsensus, ssCnsns, startRes, endRes + 1,
+      aa.label = MessageManager.getString("label.ssconsensus_label") + " " + ssSource + " " + getName();
+      aa.description = ssSource + MessageManager.getString("label.ssconsensus_label") +" for " + getName();
+      ssConsensusData = profile;
+      // preserve width if already set
+      int aWidth = (aa.annotations != null)
+              ? (endRes < aa.annotations.length
+                      ? aa.annotations.length
+                      : endRes + 1)
+              : endRes + 1;
+      aa.annotations = null;
+      aa.annotations = new Annotation[aWidth]; // should be alignment width
+    
+    AAFrequency.completeSSConsensus(aa, profile, startRes, endRes + 1,
             ignoreGapsInConsensus, showSequenceLogo, nseq); // TODO: setting
                                                             // container
+    }
     // for
     // ignoreGapsInConsensusCalculation);
   }
@@ -1206,7 +1239,7 @@ public class SequenceGroup implements AnnotatedCollectionI
     return consensus;
   }
   
-  public AlignmentAnnotation getSSConsensus()
+  public List<AlignmentAnnotation> getSSConsensus(List<String> ssSources)
   {
     // TODO get or calculate and get consensus annotation row for this group
     int aWidth = this.getWidth();
@@ -1219,13 +1252,20 @@ public class SequenceGroup implements AnnotatedCollectionI
     }
     if (ssConsensus == null)
     {
-      ssConsensus = new AlignmentAnnotation("", "", new Annotation[1], 0f,
-              100f, AlignmentAnnotation.BAR_GRAPH);
-      ssConsensus.hasText = true;
-      ssConsensus.autoCalculated = true;
-      ssConsensus.groupRef = this;
-      ssConsensus.label = "Sec Str Consensus for " + getName();
-      ssConsensus.description = "Percent Identity";
+      ssConsensus = new ArrayList<AlignmentAnnotation>();
+      
+      for(String ssSource : ssSources) {
+        AlignmentAnnotation aa = new AlignmentAnnotation("", "", new Annotation[1], 0f,
+                100f, AlignmentAnnotation.BAR_GRAPH);
+        aa.hasText = true;
+        aa.autoCalculated = true;
+        aa.groupRef = this;
+
+        aa.label = MessageManager.getString("label.ssconsensus_label") + " " + ssSource + " " + getName();
+        aa.description = ssSource + MessageManager.getString("label.ssconsensus_label") +" for " + getName();
+        ssConsensus.add(aa);
+      }
+     
     }
     return ssConsensus;
   }
index ccda002..8dfc2e6 100644 (file)
@@ -56,14 +56,14 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Deque;
 import java.util.Enumeration;
+import java.util.HashMap;
 import java.util.Hashtable;
 import java.util.List;
 import java.util.Locale;
+import java.util.Map;
 import java.util.Vector;
 
-import javax.swing.AbstractButton;
 import javax.swing.ButtonGroup;
-import javax.swing.ButtonModel;
 import javax.swing.JCheckBoxMenuItem;
 import javax.swing.JComponent;
 import javax.swing.JEditorPane;
@@ -73,7 +73,6 @@ import javax.swing.JLayeredPane;
 import javax.swing.JMenu;
 import javax.swing.JMenuItem;
 import javax.swing.JPanel;
-import javax.swing.JRadioButtonMenuItem;
 import javax.swing.JScrollPane;
 import javax.swing.SwingUtilities;
 
@@ -85,7 +84,6 @@ import jalview.analysis.Dna;
 import jalview.analysis.GeneticCodeI;
 import jalview.analysis.ParseProperties;
 import jalview.analysis.SequenceIdMatcher;
-import jalview.api.AlignCalcWorkerI;
 import jalview.api.AlignExportSettingsI;
 import jalview.api.AlignViewControllerGuiI;
 import jalview.api.AlignViewControllerI;
@@ -162,7 +160,6 @@ import jalview.util.Platform;
 import jalview.util.imagemaker.BitmapImageSizing;
 import jalview.viewmodel.AlignmentViewport;
 import jalview.viewmodel.ViewportRanges;
-import jalview.workers.SecondaryStructureConsensusThread;
 import jalview.ws.DBRefFetcher;
 import jalview.ws.DBRefFetcher.FetchFinishedListenerI;
 import jalview.ws.jws1.Discoverer;
@@ -5645,9 +5642,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   @Override
   protected void justifyLeftMenuItem_actionPerformed(ActionEvent e)
   {
-    AlignmentI al = viewport.getAlignment();
-    al.justify(false);
-    viewport.firePropertyChange("alignment", null, al);
+    avc.justify_Region(true);
   }
 
   /**
@@ -5656,9 +5651,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   @Override
   protected void justifyRightMenuItem_actionPerformed(ActionEvent e)
   {
-    AlignmentI al = viewport.getAlignment();
-    al.justify(true);
-    viewport.firePropertyChange("alignment", null, al);
+    avc.justify_Region(false);
   }
 
   @Override
@@ -5683,131 +5676,125 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   }
   
   @Override
-  protected void updateShowSSRadioButtons(JMenu showSS, ButtonGroup ssButtonGroup){
+  protected void updateShowSecondaryStructureMenu(JMenu showSS, ButtonGroup ssButtonGroup){
     
     List<String> ssSources = new ArrayList<String>();
     AlignmentAnnotation[] anns = alignPanel.getAlignment()
             .getAlignmentAnnotation();
+    Map<String, JCheckBoxMenuItem> checkboxMap = getCheckboxesInMenu(showSS);
     
     ssSources = AlignmentUtils.extractSSSourceInAlignmentAnnotation(anns);
     
-    // Get the currently selected radio button
-    String selectedButtonModelName = getSelectedRadioButtonDisplayString(ssButtonGroup);
-    boolean selectedButtonModel =  false;
+    List<String> selectedCheckBoxes = getSelectedOptions(checkboxMap);
     
-    // Clear existing radio buttons
-    showSS.removeAll();
-    JRadioButtonMenuItem radioButtonAllSS = new JRadioButtonMenuItem("All");
-    radioButtonAllSS.addActionListener(new ActionListener() {
-      @Override
-      public void actionPerformed(ActionEvent e) {
-          showSS_actionPerformed("All");
-      }
-    });
-    ssButtonGroup.add(radioButtonAllSS);
-    showSS.add(radioButtonAllSS);
-    
-    JRadioButtonMenuItem radioButtonNoneSS = new JRadioButtonMenuItem("None");
-    radioButtonNoneSS.addActionListener(new ActionListener() {
-      @Override
-      public void actionPerformed(ActionEvent e) {
-          showSS_actionPerformed("None");
+    // Add checkboxes for categories
+    for (String ssSource : ssSources) {
+      
+      if(checkboxMap.get(ssSource)== null) {
+        JCheckBoxMenuItem checkBox = new JCheckBoxMenuItem(ssSource);
+        checkBox.setSelected(false);
+                
+        checkBox.addItemListener(e -> {
+            if (e.getStateChange() == ItemEvent.SELECTED) {
+                
+              showOrHideSecondaryStructureForSource(ssSource, true);
+              
+            } else {                
+
+              showOrHideSecondaryStructureForSource(ssSource, false);
+              
+            }
+        });
+        showSS.add(checkBox);
       }
-    });
-    ssButtonGroup.add(radioButtonNoneSS);
-    showSS.add(radioButtonNoneSS);
-    showSS.addSeparator();
+    }
+    // Iterate over the keys of checkboxMap
+    for (String key : checkboxMap.keySet()) {
+        // Check if the key is not in ssSources
+        if (!ssSources.contains(key)) {
+            showSS.remove(checkboxMap.get(key));            
+            checkboxMap.remove(key);
+            selectedCheckBoxes.remove(key);
+        }
+        if(selectedCheckBoxes.contains(key)){
+          checkboxMap.get(key).setSelected(true);
+        }
+        else {
+          checkboxMap.get(key).setSelected(false);
+        }        
 
-       for(String ssSource : ssSources) {
-        
-        JRadioButtonMenuItem radioButton = new JRadioButtonMenuItem(ssSource);
-        radioButton.addActionListener(new ActionListener() {
-          @Override
-          public void actionPerformed(ActionEvent e) {
-              showSS_actionPerformed(ssSource);
-          }
-        });
-        ssButtonGroup.add(radioButton);
-        showSS.add(radioButton);
-        
-        // Check if this radio button's name matches the selected radio button's name
-        if (ssSource.equals(selectedButtonModelName)) {
-            radioButton.setSelected(true); // Select this radio button
-            selectedButtonModel = true;
+        ssButtonGroup.clearSelection();
+    }
+
+  }
+  
+  private List<String> getSelectedOptions(Map<String, JCheckBoxMenuItem> checkboxMap) {
+    List<String> selectedOptions = new ArrayList<>();
+    for (String key : checkboxMap.keySet()) {
+        JCheckBoxMenuItem checkbox = checkboxMap.get(key);
+        if (checkbox.isSelected()) {
+            selectedOptions.add(key);
         }
-        
-      } 
-
-       if (selectedButtonModelName == "None") {
-         // If no radio button was previously selected, select "All"
-         ssButtonGroup.setSelected(radioButtonNoneSS.getModel(), true);
-         selectedButtonModel = true;
-       }
-       if (!selectedButtonModel) {
-           // If no radio button was previously selected, select "All"
-           ssButtonGroup.setSelected(radioButtonAllSS.getModel(), true);
-       }
+    }
+    return selectedOptions;
   }
   
+  private Map<String, JCheckBoxMenuItem> getCheckboxesInMenu(JMenu menu) {
+    Map<String, JCheckBoxMenuItem> checkboxMap = new HashMap<>();
+    for (Component component : menu.getMenuComponents()) {
+        if (component instanceof JCheckBoxMenuItem) {
+            JCheckBoxMenuItem checkbox = (JCheckBoxMenuItem) component;
+            checkboxMap.put(checkbox.getText(), checkbox);
+        }
+    }
+    return checkboxMap;
+}
+  
   @Override
-  protected void showSS_actionPerformed(String ssSourceSelection){
+  protected void showOrHideSecondaryStructureForSource(String ssSourceSelection, boolean visible){
+    
+    String noneOption = MessageManager.getString("option.ss_providers_none");
+    String allOption = MessageManager.getString("option.ss_providers_all");
     
-
     AlignmentAnnotation[] annotations = alignPanel.getAlignment()
             .getAlignmentAnnotation();
     
     for (AlignmentAnnotation aa: annotations) {
       
+      boolean isSSConsensus = aa.label.startsWith(MessageManager.getString("label.ssconsensus_label"));
+      boolean matchesSSSourceSelection = aa.description.startsWith(ssSourceSelection);
+      
+      if(isSSConsensus && (matchesSSSourceSelection || ssSourceSelection.equals(noneOption))) {
+        
+        if (ssSourceSelection.equals(allOption)) {
+          aa.visible  = true;
+          break;
+        }
+        
+        if(!aa.description.startsWith(allOption))
+          aa.visible  = visible;
+
+      }
+      
       for (String label : Constants.SECONDARY_STRUCTURE_LABELS.keySet()) {
         
         if (label.equals(aa.label)) { 
-          
-          aa.visible = false;
-          
-          if(ssSourceSelection == "All") {
-            aa.visible = true;
-          }
-          
-          else {
+
             String ssSource = AlignmentUtils.extractSSSourceFromAnnotationDescription(aa);
-            if(ssSource.equals(ssSourceSelection)) {
-              aa.visible = true;
-            }
-            
-            
-          }
+            if(ssSource.equals(ssSourceSelection) || ssSourceSelection.equals(noneOption)) {
+              aa.visible = visible;
+            }           
         }
       }
       
     }
     
-      
-      List<AlignCalcWorkerI> workers = viewport.getCalcManager()
-              .getRegisteredWorkersOfClass(SecondaryStructureConsensusThread.class);
-      if (!workers.isEmpty()) {
-          
-        viewport.getCalcManager().startWorker(workers.remove(0));
-
-      }
-      
       PaintRefresher.Refresh(this, viewport.getSequenceSetId());
       alignPanel.updateAnnotation();
       alignPanel.paintAlignment(true, true);
-          
-    
     
   }
-  
-  protected String getSelectedRadioButtonDisplayString(ButtonGroup ssButtonGroup) {
-    Enumeration<AbstractButton> buttons = ssButtonGroup.getElements();
-    while (buttons.hasMoreElements()) {
-        AbstractButton button = buttons.nextElement();
-        if (button.isSelected()) {
-            return button.getText();
-        }
-    }
-    return null; // No radio button is selected
-}
+
 
   /*
    * (non-Javadoc)
index 4e1fcab..756a266 100644 (file)
@@ -318,7 +318,7 @@ public class AlignViewport extends AlignmentViewport
     if (residueShading != null)
     {
       residueShading.setConsensus(hconsensus);
-      residueShading.setSsConsensus(hSSConsensus);
+      residueShading.setSSConsensusProfileMap(hSSConsensusProfileMap);
     }
     setColourAppliesToAllGroups(true);
   }
index 4e22a74..8c8bf21 100644 (file)
@@ -312,7 +312,7 @@ public class CalculationChooser extends JPanel
     }
     this.add(actionPanel, BorderLayout.SOUTH);
 
-    int width = 365;
+    int width = 375;
     int height = includeParams ? 420 : 240;
 
     setMinimumSize(new Dimension(325, height - 10));
@@ -467,8 +467,13 @@ public class CalculationChooser extends JPanel
     final JComboBox<String> comboBox = new JComboBox<>();
     Object curSel = comboBox.getSelectedItem();
     DefaultComboBoxModel<String> sourcesModel = new DefaultComboBoxModel<>();
-
+    
     List<String> ssSources = getApplicableSecondaryStructureSources();
+    
+    if(ssSources == null) {
+      return comboBox;
+    }
+    ssSources.add(0, MessageManager.getString("option.ss_providers_all")); 
 
     boolean selectedIsPresent = false;
     for (String source : ssSources)
@@ -593,8 +598,8 @@ public class CalculationChooser extends JPanel
   {
     AlignmentAnnotation[] annotations = af.getViewport().getAlignment().getAlignmentAnnotation();
     
-    List<String> ssSources = AlignmentUtils.getSecondaryStructureSources(annotations);
-    //List<String> ssSources = AlignmentUtils.extractSSSourceInAlignmentAnnotation(annotations);
+    //List<String> ssSources = AlignmentUtils.getSecondaryStructureSources(annotations);
+    List<String> ssSources = AlignmentUtils.extractSSSourceInAlignmentAnnotation(annotations);
     
                  
     return ssSources;
@@ -607,16 +612,18 @@ public class CalculationChooser extends JPanel
   {
     boolean doPCA = pca.isSelected();
     String modelName = modelNames.getSelectedItem().toString();
-    String ssSource = "";
-    Object selectedItem = ssSourceDropdown.getSelectedItem();
-    if (selectedItem != null) {
-        ssSource = selectedItem.toString();
+
+    String ssSource = null;
+    
+    if (modelName.equals(secondaryStructureModelName)) {
+      Object selectedItem = ssSourceDropdown.getSelectedItem();
+      if (selectedItem != null) {
+          ssSource = selectedItem.toString();
+      }
     }
     SimilarityParams params = getSimilarityParameters(doPCA);
-    if(ssSource.length()>0)
-    {
-      params.setSecondaryStructureSource(ssSource);
-    }
+    params.setSecondaryStructureSource(ssSource);
+    
     if (doPCA)
     {
       openPcaPanel(modelName, params);
index 576f3b2..0bcb0da 100644 (file)
@@ -32,8 +32,10 @@ import java.awt.print.PrinterException;
 import java.awt.print.PrinterJob;
 
 import javax.swing.ButtonGroup;
+import javax.swing.JLabel;
 import javax.swing.JMenuItem;
 import javax.swing.JRadioButtonMenuItem;
+import javax.swing.SwingConstants;
 import javax.swing.event.InternalFrameAdapter;
 import javax.swing.event.InternalFrameEvent;
 
@@ -129,6 +131,14 @@ public class PCAPanel extends GPCAPanel
     PaintRefresher.Register(this, av.getSequenceSetId());
 
     setRotatableCanvas(new RotatableCanvas(alignPanel));
+
+    if(params.getSecondaryStructureSource()!=null ) {
+      // Initialize and set subtitle text
+      JLabel subtitleLabel = new JLabel(" Secondary Structure Provider : " 
+                  + params.getSecondaryStructureSource(), SwingConstants.LEFT);
+      this.getContentPane().add(subtitleLabel, BorderLayout.NORTH); 
+    
+    }
     this.getContentPane().add(getRotatableCanvas(), BorderLayout.CENTER);
 
     addKeyListener(getRotatableCanvas());
index d2c00eb..9d23474 100644 (file)
@@ -1388,6 +1388,29 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
         cut_actionPerformed();
       }
     });
+    JMenuItem justifyLeftMenuItem = new JMenuItem(
+            MessageManager.getString("action.left_justify"));
+    justifyLeftMenuItem.addActionListener(new ActionListener()
+    {
+
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        ap.alignFrame.avc.justify_Region(true);
+      }
+    });
+    JMenuItem justifyRightMenuItem = new JMenuItem(
+            MessageManager.getString("action.right_justify"));
+    justifyRightMenuItem.addActionListener(new ActionListener()
+    {
+
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        ap.alignFrame.avc.justify_Region(false);
+      }
+    });
+
     upperCase.setText(MessageManager.getString("label.to_upper_case"));
     upperCase.addActionListener(new ActionListener()
     {
@@ -1536,6 +1559,8 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
 
     editMenu.add(copy);
     editMenu.add(cut);
+    editMenu.add(justifyLeftMenuItem);
+    editMenu.add(justifyRightMenuItem);
     editMenu.add(editSequence);
     editMenu.add(upperCase);
     editMenu.add(lowerCase);
@@ -1741,10 +1766,16 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
     AlignmentUtils.addReferenceAnnotations(candidates, alignment, null);
         
     if(AlignmentUtils.isSSAnnotationPresent(candidates)) {
+      
       restartSSConsensusWorker();
+      ap.validateAnnotationDimensions(true);
+      ap.fontChanged();
+      ap.av.alignmentChanged(ap);
+      ap.adjustAnnotationHeight();
+      restartSSConsensusWorker();
+      //ap.alignFrame.getViewport().getCalcManager().restartWorkers();
     }
         
-    refresh();
   }
   
   
index 6132908..0e834d4 100644 (file)
@@ -1344,10 +1344,10 @@ public class StructureChooser extends GStructureChooser
             selectedSequence = userSelectedSeq;
           }
           String pdbFilename = selectedPdbFileName;
-
+          // TODO - tidy up this ugly hack so we call launchStructureViewer too
           StructureChooser.openStructureFileForSequence(ssm, sc, ap,
-                  selectedSequence, true, pdbFilename, tft, paeFilename,
-                  true);
+                  selectedSequence, true, pdbFilename, tft, paeFilename,false,
+                  true,false,getTargetedStructureViewer(ssm).getViewerType());
         }
         SwingUtilities.invokeLater(new Runnable()
         {
@@ -1806,6 +1806,22 @@ public class StructureChooser extends GStructureChooser
             paeFilename, false, true, doXferSettings, null);
   }
 
+  /**
+   * 
+   * @param ssm
+   * @param sc
+   * @param ap
+   * @param seq
+   * @param prompt
+   * @param sFilename
+   * @param tft
+   * @param paeFilename
+   * @param forceHeadless
+   * @param showRefAnnotations
+   * @param doXferSettings
+   * @param viewerType - when not null means the viewer will be opened, providing forceHeadless/headless is not true
+   * @return
+   */
   public static StructureViewer openStructureFileForSequence(
           StructureSelectionManager ssm, StructureChooser sc,
           AlignmentPanel ap, SequenceI seq, boolean prompt,
@@ -1828,7 +1844,7 @@ public class StructureChooser extends GStructureChooser
       ssm = ap.getStructureSelectionManager();
       StructureSelectionManager.doConfigureStructurePrefs(ssm);
     }
-
+    
     PDBEntry fileEntry = new AssociatePdbFileWithSeq().associatePdbWithSeq(
             sFilename, DataSourceType.FILE, seq, prompt, Desktop.instance,
             tft, paeFilename, doXferSettings);
@@ -1840,10 +1856,13 @@ public class StructureChooser extends GStructureChooser
       sv = sc.launchStructureViewer(ssm, new PDBEntry[] { fileEntry }, ap,
               new SequenceI[]
               { seq }, viewerType);
+      // foo
+      sv.getJalviewStructureDisplay().raiseViewer();
     }
 
     sc.mainFrame.dispose();
 
+    // TODO should honor preferences - only show reference annotation that is requested - JAL-4415 JAL-3124
     if (showRefAnnotations)
     {
       showReferenceAnnotationsForSequence(ap.alignFrame, seq);
index f708c48..24a5547 100755 (executable)
@@ -20,6 +20,7 @@
  */
 package jalview.gui;
 
+import java.awt.BorderLayout;
 import java.awt.Font;
 import java.awt.Graphics;
 import java.awt.event.ActionEvent;
@@ -35,8 +36,11 @@ import java.util.List;
 import java.util.Locale;
 
 import javax.swing.ButtonGroup;
+import javax.swing.JLabel;
 import javax.swing.JMenuItem;
+import javax.swing.JPanel;
 import javax.swing.JRadioButtonMenuItem;
+import javax.swing.SwingConstants;
 import javax.swing.event.InternalFrameAdapter;
 import javax.swing.event.InternalFrameEvent;
 
@@ -96,6 +100,10 @@ public class TreePanel extends GTreePanel
   TreeModel tree;
 
   private AlignViewport av;
+  
+
+  // New JLabel for subtitle
+  private JLabel subtitleLabel;
 
   /**
    * Creates a new TreePanel object.
@@ -181,6 +189,19 @@ public class TreePanel extends GTreePanel
 
     treeCanvas = new TreeCanvas(this, ap, scrollPane);
     scrollPane.setViewportView(treeCanvas);
+    
+    if(this.similarityParams!=null)
+      if(this.similarityParams.getSecondaryStructureSource()!=null ) {
+    
+      subtitleLabel = new JLabel(" Secondary Structure Provider : " 
+                + this.similarityParams.getSecondaryStructureSource(), SwingConstants.LEFT);
+  
+      JPanel panel = new JPanel(new BorderLayout());
+      panel.add(subtitleLabel, BorderLayout.NORTH);
+      panel.add(scrollPane, BorderLayout.CENTER);
+  
+      this.add(panel);
+    }
 
     if (columnWise)
     {
index 3c9f642..c93c104 100755 (executable)
@@ -22,8 +22,8 @@ package jalview.jbgui;
 
 import java.awt.BorderLayout;
 import java.awt.Color;
+import java.awt.Component;
 import java.awt.GridLayout;
-import java.awt.Menu;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
 import java.awt.event.FocusAdapter;
@@ -56,13 +56,10 @@ import jalview.analysis.GeneticCodeI;
 import jalview.analysis.GeneticCodes;
 import jalview.api.SplitContainerI;
 import jalview.bin.Cache;
-import jalview.bin.Console;
 import jalview.gui.JvSwingUtils;
 import jalview.gui.Preferences;
 import jalview.io.FileFormats;
-import jalview.log.JLoggerLog4j;
 import jalview.schemes.ResidueColourScheme;
-import jalview.util.Log4j;
 import jalview.util.MessageManager;
 import jalview.util.Platform;
 
@@ -199,10 +196,6 @@ public class GAlignFrame extends JInternalFrame
 
   protected JMenuItem expandViews = new JMenuItem();
   
-  protected JCheckBoxMenuItem threeDStructure = new JCheckBoxMenuItem();
-
-  protected JCheckBoxMenuItem jPred = new JCheckBoxMenuItem();
-
   protected JCheckBoxMenuItem showGroupConsensus = new JCheckBoxMenuItem();
 
   protected JCheckBoxMenuItem showGroupConservation = new JCheckBoxMenuItem();
@@ -452,7 +445,9 @@ public class GAlignFrame extends JInternalFrame
     addMenuActionAndAccelerator(keyStroke, removeAllGapsMenuItem, al);
 
     JMenuItem justifyLeftMenuItem = new JMenuItem(
-            MessageManager.getString("action.left_justify_alignment"));
+            MessageManager.getString("action.left_justify"));
+    justifyLeftMenuItem.setToolTipText(
+            MessageManager.getString("tooltip.left_justify"));
     justifyLeftMenuItem.addActionListener(new ActionListener()
     {
       @Override
@@ -462,7 +457,10 @@ public class GAlignFrame extends JInternalFrame
       }
     });
     JMenuItem justifyRightMenuItem = new JMenuItem(
-            MessageManager.getString("action.right_justify_alignment"));
+            MessageManager.getString("action.right_justify"));
+    justifyRightMenuItem.setToolTipText(
+            MessageManager.getString("action.left_justify"));
+
     justifyRightMenuItem.addActionListener(new ActionListener()
     {
       @Override
@@ -1802,42 +1800,8 @@ public class GAlignFrame extends JInternalFrame
     };
     addMenuActionAndAccelerator(keyStroke, copyHighlighted, al);
     copyHighlighted.addActionListener(al);
-    
-    
 
-    ButtonGroup ssButtonGroup = new ButtonGroup();
-    final JRadioButtonMenuItem showAll = new JRadioButtonMenuItem(
-            MessageManager.getString("label.show_first"));
-    final JRadioButtonMenuItem showjPred = new JRadioButtonMenuItem(
-            MessageManager.getString("label.show_last"));
-    final JRadioButtonMenuItem show3DSS = new JRadioButtonMenuItem(
-            MessageManager.getString("label.show_last"));
-    ssButtonGroup.add(showAll);
-    ssButtonGroup.add(showjPred);
-    ssButtonGroup.add(show3DSS);
-    final boolean autoFirst1 = Cache
-            .getDefault(Preferences.SHOW_AUTOCALC_ABOVE, false);
-    showAll.setSelected(autoFirst1);
-    setShowAutoCalculatedAbove(autoFirst);
-    showAutoFirst.addActionListener(new ActionListener()
-    {
-      @Override
-      public void actionPerformed(ActionEvent e)
-      {
-        setShowAutoCalculatedAbove(showAutoFirst.isSelected());
-        sortAnnotations_actionPerformed();
-      }
-    });
-    showAutoLast.setSelected(!showAutoFirst.isSelected());
-    showAutoLast.addActionListener(new ActionListener()
-    {
-      @Override
-      public void actionPerformed(ActionEvent e)
-      {
-        setShowAutoCalculatedAbove(!showAutoLast.isSelected());
-        sortAnnotations_actionPerformed();
-      }
-    });
+    ButtonGroup ssButtonGroup = new ButtonGroup();   
 
     JMenu tooltipSettingsMenu = new JMenu(
             MessageManager.getString("label.sequence_id_tooltip"));
@@ -1847,12 +1811,47 @@ public class GAlignFrame extends JInternalFrame
     JMenu showSS = new JMenu(
             MessageManager.getString("label.show_secondary_structure")); 
     
-    showSS.addMouseListener(new MouseAdapter() {
+    JRadioButtonMenuItem radioButtonAllSS = new JRadioButtonMenuItem(MessageManager.getString("option.ss_providers_all"));
+    radioButtonAllSS.addActionListener(new ActionListener() {
+      @Override
+      public void actionPerformed(ActionEvent e) {
+          showOrHideSecondaryStructureForSource(MessageManager.getString("option.ss_providers_all"), true);
+          // Select all checkboxes if "All" is selected
+          Component[] components = showSS.getMenuComponents();
+          for (Component component : components) {
+              if (component instanceof JCheckBoxMenuItem) {
+                  ((JCheckBoxMenuItem) component).setSelected(true);
+              }
+          }
+      }
+    });
+    ssButtonGroup.add(radioButtonAllSS);
+    showSS.add(radioButtonAllSS);
+    
+    JRadioButtonMenuItem radioButtonNoneSS = new JRadioButtonMenuItem(MessageManager.getString("option.ss_providers_none"));
+    radioButtonNoneSS.addActionListener(new ActionListener() {
+      @Override
+      public void actionPerformed(ActionEvent e) {
+          showOrHideSecondaryStructureForSource(MessageManager.getString("option.ss_providers_none"), false);
+          // Remove selection of all checkboxes if "None" is selected
+          Component[] components = showSS.getMenuComponents();
+          for (Component component : components) {
+              if (component instanceof JCheckBoxMenuItem) {
+                  ((JCheckBoxMenuItem) component).setSelected(false);
+              }
+          }
+      }
+    });
+    ssButtonGroup.add(radioButtonNoneSS);
+    showSS.add(radioButtonNoneSS);
+    showSS.addSeparator();
+    
+    annotationsMenu.addMouseListener(new MouseAdapter() {
       
       @Override
       public void mouseEntered(MouseEvent e) {
 
-          updateShowSSRadioButtons(showSS, ssButtonGroup); // Update radio buttons every time the menu is clicked
+        updateShowSecondaryStructureMenu(showSS, ssButtonGroup); // Update radio buttons every time the menu is clicked
          
       }
     });
@@ -1912,11 +1911,11 @@ public class GAlignFrame extends JInternalFrame
     editMenu.add(removeAllGapsMenuItem);
     editMenu.add(removeRedundancyMenuItem);
     editMenu.addSeparator();
-    // dont add these yet in the CVS build - they cannot be undone!
-    // Excluded from Jalview 2.5 release - undo needs to be implemented.
-    // editMenu.add(justifyLeftMenuItem);
-    // editMenu.add(justifyRightMenuItem);
-    // editMenu.addSeparator();
+
+    editMenu.add(justifyLeftMenuItem);
+    editMenu.add(justifyRightMenuItem);
+
+    editMenu.addSeparator();
     editMenu.add(padGapsMenuitem);
 
     showMenu.add(showAllColumns);
@@ -2599,10 +2598,6 @@ public class GAlignFrame extends JInternalFrame
 
   }
 
-  protected void jpred_actionPerformed(ActionEvent e)
-  {
-  }
-
   protected void scaleAbove_actionPerformed(ActionEvent e)
   {
   }
@@ -2873,13 +2868,14 @@ public class GAlignFrame extends JInternalFrame
     return null;
   }
 
-  protected void updateShowSSRadioButtons(JMenu showSS,
-          ButtonGroup ssButtonGroup)
+  protected void showOrHideSecondaryStructureForSource(String ssSourceSelection, boolean visible)
   {
     // TODO Auto-generated method stub
+    
   }
 
-  protected void showSS_actionPerformed(String ssSourceSelection)
+  protected void updateShowSecondaryStructureMenu(JMenu showSS,
+          ButtonGroup ssButtonGroup)
   {
     // TODO Auto-generated method stub
     
index 2f4052a..e620c1c 100644 (file)
@@ -5310,7 +5310,7 @@ public class Jalview2XML
     viewport.getResidueShading()
             .setConsensus(viewport.getSequenceConsensusHash());
     viewport.getResidueShading()
-    .setSsConsensus(viewport.getSequenceSSConsensusHash());
+    .setSSConsensusProfileMap(viewport.getSequenceSSConsensusHash());
     if (safeBoolean(view.isConservationSelected()) && cs != null)
     {
       viewport.getResidueShading()
index 4e9f669..2b73002 100644 (file)
@@ -33,6 +33,8 @@ import java.awt.geom.AffineTransform;
 import java.awt.image.ImageObserver;
 import java.util.BitSet;
 import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
 
 import org.jfree.graphics2d.svg.SVGGraphics2D;
 import org.jibble.epsgraphics.EpsGraphics2D;
@@ -91,7 +93,7 @@ public class AnnotationRenderer
 
   private ProfilesI hconsensus;
   
-  private ProfilesI hSSconsensus;
+  private Map<String, ProfilesI> hSSconsensus;
 
   private Hashtable<String, Object>[] complementConsensus;
 
@@ -432,16 +434,21 @@ public class AnnotationRenderer
       }
     }
     
-    else if(aa.autoCalculated && aa.label.startsWith(MessageManager.getString("label.ssconsensus_label"))) 
+    if(aa.autoCalculated && aa.label.startsWith(MessageManager.getString("label.ssconsensus_label"))) 
     {
-      return AAFrequency.extractProfile(
-              hSSconsensus.get(column),
-              av_ignoreGapsConsensus);
+      if(hSSconsensus!=null) {
+      for (String source : hSSconsensus.keySet()) {
+        if(aa.description.startsWith(source)) {
+      
+          return AAFrequency.extractProfile(
+                  hSSconsensus.get(source).get(column),
+                  av_ignoreGapsConsensus);
+        }
+      }
+      }
       
     }
     
-    else
-    {
       if (aa.autoCalculated && aa.label.startsWith("StrucConsensus"))
       {
         // TODO implement group structure consensus
@@ -463,7 +470,7 @@ public class AnnotationRenderer
                   av_ignoreGapsConsensus);
         }
       }
-    }
+    
     return null;
   }
 
@@ -529,7 +536,7 @@ public class AnnotationRenderer
             .getAlignmentStrucConsensusAnnotation();
     final AlignmentAnnotation complementConsensusAnnot = av
             .getComplementConsensusAnnotation();
-    final AlignmentAnnotation ssConsensusAnnot = av
+    final List<AlignmentAnnotation> ssConsensusAnnot = av
             .getAlignmentSecondaryStructureConsensusAnnotation();
 
     BitSet graphGroupDrawn = new BitSet();
@@ -557,7 +564,7 @@ public class AnnotationRenderer
         normaliseProfile = row.groupRef.isNormaliseSequenceLogo();
       }
       else if (row == consensusAnnot || row == structConsensusAnnot
-              || row == complementConsensusAnnot || row == ssConsensusAnnot)
+              || row == complementConsensusAnnot || (ssConsensusAnnot!=null && ssConsensusAnnot.contains(row)))
       {
         renderHistogram = av_renderHistogram;
         renderProfile = av_renderProfile;
@@ -1619,7 +1626,7 @@ public class AnnotationRenderer
               colour = profcolour.findColour(codonTranslation.charAt(0),
                       column, null);
             }
-            if(_aa.label == MessageManager.getString("label.ssconsensus_label")) {
+            if(_aa.label.startsWith(MessageManager.getString("label.ssconsensus_label"))) {
               colour = AlignmentUtils.getSecondaryStructureAnnotationColour(dc[0]);              
             }
             else
index b914c65..9231901 100644 (file)
@@ -63,20 +63,10 @@ public class ResidueShader implements ResidueShaderI
   private ProfilesI consensus;
   
   /*
-   * the consensus data for each column
+   * the ss consensus data for each column for each source
    */
-  private ProfilesI ssConsensus;
   
-
-  public ProfilesI getSsConsensus()
-  {
-    return ssConsensus;
-  }
-
-  public void setSsConsensus(ProfilesI ssConsensus)
-  {
-    this.ssConsensus = ssConsensus;
-  }
+  private Map<String, ProfilesI> ssConsensusProfileMap;
 
   /*
    * if true, apply shading of colour by conservation
@@ -144,7 +134,7 @@ public class ResidueShader implements ResidueShaderI
     this.conservationIncrement = rs.conservationIncrement;
     this.ignoreGaps = rs.ignoreGaps;
     this.pidThreshold = rs.pidThreshold;
-    this.ssConsensus = rs.ssConsensus;
+    this.ssConsensusProfileMap = rs.ssConsensusProfileMap;
   }
 
   /**
@@ -281,7 +271,7 @@ public class ResidueShader implements ResidueShaderI
   }
   
   @Override
-  public Color findSSColour(char symbol, int position, SequenceI seq)
+  public Color findSSColour(char symbol, int position, SequenceI seq, String source)
   {
     if (colourScheme == null)
     {
@@ -291,7 +281,7 @@ public class ResidueShader implements ResidueShaderI
     /*
      * get 'base' colour
      */
-    ProfileI profile = ssConsensus == null ? null : ssConsensus.get(position);
+    ProfileI profile = ssConsensusProfileMap.get(source) == null ? null : ssConsensusProfileMap.get(source).get(position);
     String modalSS = profile == null ? null
             : profile.getModalSS();
     float pid = profile == null ? 0f
@@ -463,4 +453,14 @@ public class ResidueShader implements ResidueShaderI
   {
     colourScheme = cs;
   }
+
+  public Map<String, ProfilesI> getSSConsensusProfileMap()
+  {
+    return ssConsensusProfileMap;
+  }
+
+  public void setSSConsensusProfileMap(Map<String, ProfilesI> ssConsensusProfileMap)
+  {
+    this.ssConsensusProfileMap = ssConsensusProfileMap;
+  }
 }
index 0412d21..42f4e2e 100644 (file)
@@ -35,7 +35,7 @@ public interface ResidueShaderI
 
   public abstract void setConsensus(ProfilesI cons);
   
-  public abstract void setSsConsensus(ProfilesI cons);
+  public abstract void setSSConsensusProfileMap(Map<String, ProfilesI> ssConsensusProfileMap);
 
   public abstract boolean conservationApplied();
 
@@ -84,6 +84,7 @@ public interface ResidueShaderI
 
   public abstract void setColourScheme(ColourSchemeI cs);
 
-  Color findSSColour(char symbol, int position, SequenceI seq);
+  Color findSSColour(char symbol, int position, SequenceI seq,
+          String source);
 
 }
\ No newline at end of file
index 6ad2f20..093410d 100644 (file)
@@ -25,6 +25,7 @@ import java.beans.PropertyChangeSupport;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.BitSet;
+import java.util.Collections;
 import java.util.Deque;
 import java.util.HashMap;
 import java.util.Hashtable;
@@ -33,6 +34,7 @@ import java.util.List;
 import java.util.Map;
 
 import jalview.analysis.AnnotationSorter.SequenceAnnotationOrder;
+import jalview.analysis.AlignmentUtils;
 import jalview.analysis.Conservation;
 import jalview.analysis.TreeModel;
 import jalview.api.AlignCalcManagerI;
@@ -699,7 +701,9 @@ public abstract class AlignmentViewport
 
   protected AlignmentAnnotation consensus;
   
-  protected AlignmentAnnotation secondaryStructureConsensus;
+  protected List<AlignmentAnnotation> secondaryStructureConsensus;
+
+  protected List<String> secondaryStructureSources;
 
   protected AlignmentAnnotation complementConsensus;
 
@@ -722,7 +726,7 @@ public abstract class AlignmentViewport
    */
   protected ProfilesI hconsensus = null;
   
-  protected ProfilesI hSSConsensus = null;
+  protected Map<String, ProfilesI> hSSConsensusProfileMap = null;
   
   
 
@@ -744,6 +748,32 @@ public abstract class AlignmentViewport
   {
     hconservation = cons;
   }
+  
+  @Override
+  public List<String> getSecondaryStructureSources()
+  {
+    return secondaryStructureSources;
+  }
+
+  @Override
+  public void setSecondaryStructureSources(
+          List<String> secondaryStructureSources)
+  {
+    this.secondaryStructureSources = secondaryStructureSources;
+  }
+  
+  protected void setSecondaryStructureSources(AlignmentAnnotation[] aa)
+  {
+    List<String> sources = null;
+    
+    if(aa!=null) {
+      sources = AlignmentUtils.extractSSSourceInAlignmentAnnotation(aa);
+      if(sources != null) {
+        sources.add(0,MessageManager.getString("option.ss_providers_all"));
+        setSecondaryStructureSources(sources);
+      }
+    }
+  }
 
   /**
    * percentage gaps allowed in a column before all amino acid properties should
@@ -764,11 +794,11 @@ public abstract class AlignmentViewport
   }
   
   @Override
-  public void setSequenceSSConsensusHash(ProfilesI hSSConsensus)
+  public void setSequenceSSConsensusHash(Map<String, ProfilesI> hSSConsensusProfileMap)
   {
-    this.hSSConsensus = hSSConsensus;
+    this.hSSConsensusProfileMap = hSSConsensusProfileMap;
   }
-  
+    
   @Override
   public void setComplementConsensusHash(
           Hashtable<String, Object>[] hconsensus)
@@ -781,12 +811,13 @@ public abstract class AlignmentViewport
   {
     return hconsensus;
   }
-  
+
   @Override
-  public ProfilesI getSequenceSSConsensusHash()
+  public Map<String, ProfilesI> getSequenceSSConsensusHash()
   {
-    return hSSConsensus;
+    return hSSConsensusProfileMap;
   }
+  
 
   @Override
   public Hashtable<String, Object>[] getComplementConsensusHash()
@@ -828,7 +859,7 @@ public abstract class AlignmentViewport
 
   
   @Override
-  public AlignmentAnnotation getAlignmentSecondaryStructureConsensusAnnotation()
+  public List<AlignmentAnnotation> getAlignmentSecondaryStructureConsensusAnnotation()
   {
     return secondaryStructureConsensus;
   }
@@ -930,7 +961,7 @@ public abstract class AlignmentViewport
   
 
   /**
-   * trigger update of consensus annotation
+   * trigger update of Secondary Structure consensus annotation
    */
   public void updateSecondaryStructureConsensus(final AlignmentViewPanel ap)
   {
@@ -939,11 +970,42 @@ public abstract class AlignmentViewport
     {
       return;
     }
+    if (secondaryStructureConsensus.size() != secondaryStructureSources.size()) {
+      
+      for(String source : secondaryStructureSources) {      
+        boolean ssConsensusForSourcePresent = false;
+        for(AlignmentAnnotation aa : secondaryStructureConsensus) {
+          if(aa.description.startsWith(source)) {
+            ssConsensusForSourcePresent = true;
+            break;
+          }
+        }
+        
+        if(!ssConsensusForSourcePresent) {
+          AlignmentAnnotation ssConsensus = new AlignmentAnnotation(MessageManager.getString("label.ssconsensus_label") + " "+source,
+                  source + MessageManager.getString("label.ssconsensus_descr"),
+                  new Annotation[1], 0f, 100f, AlignmentAnnotation.BAR_GRAPH);
+
+          ssConsensus.hasText = true;
+          ssConsensus.autoCalculated = true;
+          secondaryStructureConsensus.add(ssConsensus);
+          if (showSSConsensus)
+          {
+            ssConsensus.visible = true;
+            alignment.addAnnotation(ssConsensus);
+            
+          }
+        }
+      }
+    }
     if (calculator
             .getRegisteredWorkersOfClass(SecondaryStructureConsensusThread.class) == null)
     {
       calculator.registerWorker(new SecondaryStructureConsensusThread(this, ap));
     }
+    ap.adjustAnnotationHeight();
+
+    
   }
 
   // --------START Structure Conservation
@@ -1463,6 +1525,12 @@ public abstract class AlignmentViewport
     changeSupport.firePropertyChange(prop, oldvalue, newvalue);
   }
 
+  @Override
+  public void notifyAlignmentChanged()
+  {
+    firePropertyChange("alignment", null, alignment);
+  }
+
   // common hide/show column stuff
 
   public void hideSelectedColumns()
@@ -2042,10 +2110,21 @@ public abstract class AlignmentViewport
       consensus = new AlignmentAnnotation("Consensus",
               MessageManager.getString("label.consensus_descr"),
               new Annotation[1], 0f, 100f, AlignmentAnnotation.BAR_GRAPH);
-      
-      secondaryStructureConsensus = new AlignmentAnnotation(MessageManager.getString("label.ssconsensus_label"),
-              MessageManager.getString("label.ssconsensus_descr"),
-              new Annotation[1], 0f, 100f, AlignmentAnnotation.BAR_GRAPH);
+      setSecondaryStructureSources(alignment.getAlignmentAnnotation());
+      List<String> secondaryStructureSources = getSecondaryStructureSources();
+
+      if(secondaryStructureSources!=null) {
+
+        secondaryStructureConsensus = new ArrayList<AlignmentAnnotation>();
+        for (String ssSource : secondaryStructureSources) {
+        
+          AlignmentAnnotation ssConsensus = new AlignmentAnnotation(MessageManager.getString("label.ssconsensus_label") + " "+ssSource,
+                  ssSource + MessageManager.getString("label.ssconsensus_descr"),
+                  new Annotation[1], 0f, 100f, AlignmentAnnotation.BAR_GRAPH);
+          secondaryStructureConsensus.add(ssConsensus);
+        }
+        
+      }
       
       initConsensus(consensus);
       initSSConsensus(secondaryStructureConsensus);
@@ -2106,14 +2185,20 @@ public abstract class AlignmentViewport
     }
   }
   
-  private void initSSConsensus(AlignmentAnnotation aa)
+  private void initSSConsensus(List<AlignmentAnnotation> secondaryStructureConsensuses)
   {
-    aa.hasText = true;
-    aa.autoCalculated = true;
-
-    if (showSSConsensus)
-    {
-      alignment.addAnnotation(aa);
+    if(secondaryStructureConsensuses == null) {
+      return;
+    }
+    for(AlignmentAnnotation aa : secondaryStructureConsensuses) {
+      aa.hasText = true;
+      aa.autoCalculated = true;
+  
+      if (showSSConsensus)
+      {
+        alignment.addAnnotation(aa);
+      }
+      
     }
   }
 
@@ -2317,7 +2402,16 @@ public abstract class AlignmentViewport
         {
           updateCalcs = true;
           alignment.addAnnotation(sg.getConsensus(), 0);
-          alignment.addAnnotation(sg.getSSConsensus(), 0);
+          
+          List<String> secondaryStructureSources = getSecondaryStructureSources();
+          if(secondaryStructureSources !=null) {
+            List<AlignmentAnnotation> ssAa = sg.getSSConsensus(secondaryStructureSources);
+            if(ssAa != null) {
+              for(AlignmentAnnotation aa : ssAa) {
+                alignment.addAnnotation(aa, 0);
+              }
+            }
+          }
         }
         // refresh the annotation rows
         if (updateCalcs)
@@ -3108,6 +3202,7 @@ public abstract class AlignmentViewport
     return sq;
   }
   
+  //to do jal-4386
   public SequenceI getSSConsensusSeq()
   {
     if (secondaryStructureConsensus == null)
@@ -3119,23 +3214,23 @@ public abstract class AlignmentViewport
       return null;
     }
     StringBuffer seqs = new StringBuffer();
-    for (int i = 0; i < secondaryStructureConsensus.annotations.length; i++)
-    {
-      Annotation annotation = secondaryStructureConsensus.annotations[i];
-      if (annotation != null)
-      {
-        String description = annotation.description;
-        if (description != null && description.startsWith("["))
-        {
-          // consensus is a tie - just pick the first one
-          seqs.append(description.charAt(1));
-        }
-        else
-        {
-          seqs.append(annotation.displayCharacter);
-        }
-      }
-    }
+//    for (int i = 0; i < secondaryStructureConsensus.annotations.length; i++)
+//    {
+//      Annotation annotation = secondaryStructureConsensus.annotations[i];
+//      if (annotation != null)
+//      {
+//        String description = annotation.description;
+//        if (description != null && description.startsWith("["))
+//        {
+//          // consensus is a tie - just pick the first one
+//          seqs.append(description.charAt(1));
+//        }
+//        else
+//        {
+//          seqs.append(annotation.displayCharacter);
+//        }
+//      }
+//    }
 
     SequenceI sq = new Sequence("Sec Str Consensus", seqs.toString());
     sq.setDescription("Percentage Identity Sec Str Consensus "
index 04cefe5..282b337 100644 (file)
  */
 package jalview.workers;
 
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
 import jalview.analysis.AAFrequency;
+import jalview.analysis.AlignmentUtils;
 import jalview.api.AlignViewportI;
 import jalview.api.AlignmentViewPanel;
 import jalview.datamodel.AlignmentAnnotation;
@@ -29,6 +35,7 @@ import jalview.datamodel.Annotation;
 import jalview.datamodel.ProfilesI;
 import jalview.datamodel.SequenceI;
 import jalview.renderer.ResidueShaderI;
+import jalview.util.MessageManager;
 
 public class SecondaryStructureConsensusThread extends AlignCalcWorker
 {
@@ -49,7 +56,7 @@ public class SecondaryStructureConsensusThread extends AlignCalcWorker
     // long started = System.currentTimeMillis();
     try
     {
-      AlignmentAnnotation ssConsensus = getSSConsensusAnnotation();
+      List<AlignmentAnnotation> ssConsensus = getSSConsensusAnnotation();
       AlignmentAnnotation gap = getGapAnnotation();
       if ((ssConsensus == null && gap == null) || calcMan.isPending(this))
       {
@@ -84,7 +91,8 @@ public class SecondaryStructureConsensusThread extends AlignCalcWorker
         calcMan.workerComplete(this);
         return;
       }
-
+      
+      setSecondaryStructureSources();
       eraseSSConsensus(aWidth);
       computeSSConsensus(alignment);
       updateResultAnnotation(true);
@@ -115,10 +123,12 @@ public class SecondaryStructureConsensusThread extends AlignCalcWorker
    */
   protected void eraseSSConsensus(int aWidth)
   {
-    AlignmentAnnotation ssConsensus = getSSConsensusAnnotation();
-    if (ssConsensus != null)
-    {
-      ssConsensus.annotations = new Annotation[aWidth];
+    List<AlignmentAnnotation> ssConsensuses = getSSConsensusAnnotation();
+    for(AlignmentAnnotation ssConsensus : ssConsensuses) {
+      if (ssConsensus != null)
+      {
+        ssConsensus.annotations = new Annotation[aWidth];
+      }
     }
     AlignmentAnnotation gap = getGapAnnotation();
     if (gap != null)
@@ -135,11 +145,17 @@ public class SecondaryStructureConsensusThread extends AlignCalcWorker
 
     SequenceI[] aseqs = getSequences();
     int width = alignment.getWidth();
-    ProfilesI hSSConsensus = AAFrequency.calculateSS(aseqs, width, 0, width,
-            true);
+    Map<String, ProfilesI> hSSConsensusProfileMap = new HashMap<String, ProfilesI>();
+    List <String> ssSources = getSecondaryStructureSources();
+    for(String ssSource : ssSources) {
+      ProfilesI hSSConsensus = AAFrequency.calculateSS(aseqs, width, 0, width,
+              true, ssSource);
+      hSSConsensusProfileMap.put(ssSource, hSSConsensus);
+    }
+    
 
-    alignViewport.setSequenceSSConsensusHash(hSSConsensus);
-    setColourSchemeConsensus(hSSConsensus);
+    alignViewport.setSequenceSSConsensusHash(hSSConsensusProfileMap);
+    setColourSchemeConsensus(hSSConsensusProfileMap);
   }
 
   /**
@@ -153,12 +169,12 @@ public class SecondaryStructureConsensusThread extends AlignCalcWorker
   /**
    * @param hconsensus
    */
-  protected void setColourSchemeConsensus(ProfilesI hSSconsensus)
+  protected void setColourSchemeConsensus(Map<String, ProfilesI> ssConsensusProfileMap)
   {
     ResidueShaderI cs = alignViewport.getResidueShading();
     if (cs != null)
     {
-      cs.setSsConsensus(hSSconsensus);
+      cs.setSSConsensusProfileMap(ssConsensusProfileMap);
     }
   }
 
@@ -167,10 +183,33 @@ public class SecondaryStructureConsensusThread extends AlignCalcWorker
    * 
    * @return
    */
-  protected AlignmentAnnotation getSSConsensusAnnotation()
+  protected List<AlignmentAnnotation> getSSConsensusAnnotation()
   {
     return alignViewport.getAlignmentSecondaryStructureConsensusAnnotation();
   }
+  
+  /**
+   * Get the Consensus annotation for the alignment
+   * 
+   * @return
+   */
+  protected void setSecondaryStructureSources()
+  {
+    List<String> sources = null;
+    AlignmentAnnotation[] aa = alignViewport.getAlignment().getAlignmentAnnotation();
+    if(aa!=null) {
+      sources = AlignmentUtils.extractSSSourceInAlignmentAnnotation(aa);
+      if(sources != null) {
+        sources.add(0, MessageManager.getString("option.ss_providers_all"));
+        alignViewport.setSecondaryStructureSources(sources);
+      }
+    }
+  }
+  
+  protected List<String> getSecondaryStructureSources()
+  {
+    return alignViewport.getSecondaryStructureSources();
+  }
 
   /**
    * Get the Gap annotation for the alignment
@@ -194,18 +233,30 @@ public class SecondaryStructureConsensusThread extends AlignCalcWorker
 
   public void updateResultAnnotation(boolean immediate)
   {
-    AlignmentAnnotation ssConsensus = getSSConsensusAnnotation();
-    ProfilesI hSSConsensus = (ProfilesI) getViewportSSConsensus();
+    List<AlignmentAnnotation> ssConsensuses = getSSConsensusAnnotation();
+    Map<String, ProfilesI> ssConsensusProfileMap = getViewportSSConsensus();
+    for(AlignmentAnnotation ssConsensus : ssConsensuses) {
+      ProfilesI ssConsensusProfile = null;
+      for(String source: ssConsensusProfileMap.keySet()) {
+        if(ssConsensus.description.startsWith(source)) {
+          ssConsensusProfile = ssConsensusProfileMap.get(source);
+          break;
+        }
+      }
+    if(ssConsensusProfile==null) {
+      continue;
+    }
     if (immediate || !calcMan.isWorking(this) && ssConsensus != null
-            && hSSConsensus != null)
+            && ssConsensusProfile != null)
     {
-      deriveSSConsensus(ssConsensus, hSSConsensus);
+      deriveSSConsensus(ssConsensus, ssConsensusProfile);
       AlignmentAnnotation gap = getGapAnnotation();
       if (gap != null)
       {
-        deriveGap(gap, hSSConsensus);
+        deriveGap(gap, ssConsensusProfile);
       }
     }
+    }
   }
 
   /**
@@ -217,12 +268,12 @@ public class SecondaryStructureConsensusThread extends AlignCalcWorker
    * @param hconsensus
    *          the computed consensus data
    */
-  protected void deriveSSConsensus(AlignmentAnnotation ssConsensusAnnotation,
+  protected void deriveSSConsensus(AlignmentAnnotation ssConsensus,
           ProfilesI hSSConsensus)
   {
 
     long nseq = getSequences().length;
-    AAFrequency.completeSSConsensus(ssConsensusAnnotation, hSSConsensus,
+    AAFrequency.completeSSConsensus(ssConsensus, hSSConsensus,
             hSSConsensus.getStartColumn(), hSSConsensus.getEndColumn() + 1,
             alignViewport.isIgnoreGapsConsensus(),
             alignViewport.isShowSequenceLogo(), nseq);
@@ -250,7 +301,7 @@ public class SecondaryStructureConsensusThread extends AlignCalcWorker
    * 
    * @return
    */
-  protected Object getViewportSSConsensus()
+  protected Map<String, ProfilesI> getViewportSSConsensus()
   {
     // TODO convert ComplementConsensusThread to use Profile
     return alignViewport.getSequenceSSConsensusHash();
index 8e04cdd..1255c58 100644 (file)
@@ -36,10 +36,13 @@ import jalview.datamodel.SequenceI;
 import jalview.datamodel.features.SequenceFeatures;
 import jalview.gui.JvOptionPane;
 
+import java.nio.charset.Charset;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
 import java.util.Map;
+import java.util.spi.LocaleServiceProvider;
 
 import org.testng.Assert;
 import org.testng.annotations.BeforeClass;
@@ -1179,4 +1182,63 @@ public class EditCommandTest
     assertEquals(10, sf.getBegin());
     assertEquals(11, sf.getEnd());
   }
+  private SequenceI mkDs(SequenceI as)
+  {
+    SequenceI ds = as.createDatasetSequence();
+    ds.setSequence(ds.getSequenceAsString().toUpperCase(Locale.ROOT));
+    return ds;
+  }
+  /**
+   * Test that mimics 'remove all gapped columns' action. This generates a
+   * series Delete Gap edits that each act on all sequences that share a gapped
+   * column region.
+   */
+  @Test(groups = { "Functional" })
+  public void testLeftRight_Justify_and_preserves_gaps()
+  {
+    EditCommand command = new EditCommand();
+    String original1 = "--ABc--DEF";
+    String original2 = "-G-Hi--J";
+    String original3 = "-M-No--PQ";
+
+    /*
+     * Set up the sequence array for operations
+     */
+    SequenceI seq1 = new Sequence("sq1", original1);
+    SequenceI ds1 = mkDs(seq1);
+    /*
+     * and check we are preserving data - if the calls below fail, something has broken the Jalview dataset derivation process
+     */
+    assertEquals("ABCDEF", seq1.getDatasetSequence().getSequenceAsString());
+    assertEquals(original1,seq1.getSequenceAsString());
+    SequenceI seq2 = new Sequence("sq2",original2);
+    SequenceI ds2 = mkDs(seq2);
+    SequenceI seq3 = new Sequence("sq3", original3);
+    SequenceI ds3 = mkDs(seq3);
+    List<SequenceI> sqs = Arrays.asList( seq1, seq2, seq3 );
+    Alignment al = new Alignment(sqs.toArray(new SequenceI[0]));
+    EditCommand lefj = new JustifyLeftOrRightCommand("Left J", true, sqs, 1, 7, al);
+    String exp = "-ABcD---EF";
+    // check without case conservation
+    assertEquals(exp.toUpperCase(Locale.ROOT),seq1.getSequenceAsString().toUpperCase(Locale.ROOT));
+    // check case
+    assertEquals(exp,seq1.getSequenceAsString());
+    // and other seqs
+    assertEquals("-GHiJ---",seq2.getSequenceAsString());
+    assertEquals("-MNoP---Q",seq3.getSequenceAsString());
+    lefj.undoCommand(new AlignmentI[] { al});
+    assertEquals(original3,seq3.getSequenceAsString());
+    assertEquals(original1,seq1.getSequenceAsString());
+    assertEquals(original2,seq2.getSequenceAsString());
+    
+    EditCommand righj = new JustifyLeftOrRightCommand("Right J", false, sqs, 2, 7, al);
+    assertEquals("----ABcDEF",seq1.getSequenceAsString());
+    assertEquals("-G---HiJ",seq2.getSequenceAsString());
+    assertEquals("-M---NoPQ",seq3.getSequenceAsString());
+    righj.undoCommand(new AlignmentI[] { al});
+    assertEquals(original3,seq3.getSequenceAsString());
+    assertEquals(original1,seq1.getSequenceAsString());
+    assertEquals(original2,seq2.getSequenceAsString());
+        
+  }
 }