spike branch updated from latest features/JAL-2446
authorgmungoc <g.m.carstairs@dundee.ac.uk>
Mon, 24 Jul 2017 12:43:38 +0000 (14:43 +0200)
committergmungoc <g.m.carstairs@dundee.ac.uk>
Mon, 24 Jul 2017 12:43:38 +0000 (14:43 +0200)
87 files changed:
help/html/features/AnnotationColumnSelectionWithSM.png [new file with mode: 0644]
help/html/features/columnFilterByAnnotation.html
help/html/releases.html
resources/lang/Messages.properties
resources/lang/Messages_es.properties
src/jalview/api/FeatureColourI.java
src/jalview/appletgui/APopupMenu.java
src/jalview/appletgui/AlignFrame.java
src/jalview/appletgui/AlignViewport.java
src/jalview/appletgui/AlignmentPanel.java
src/jalview/appletgui/AnnotationColumnChooser.java
src/jalview/appletgui/AnnotationLabels.java
src/jalview/appletgui/AnnotationPanel.java
src/jalview/appletgui/FeatureColourChooser.java
src/jalview/appletgui/FeatureRenderer.java
src/jalview/appletgui/FeatureSettings.java
src/jalview/appletgui/IdCanvas.java
src/jalview/appletgui/OverviewCanvas.java
src/jalview/appletgui/OverviewPanel.java
src/jalview/appletgui/ScalePanel.java
src/jalview/appletgui/SeqCanvas.java
src/jalview/appletgui/SeqPanel.java
src/jalview/appletgui/SliderPanel.java
src/jalview/datamodel/Alignment.java
src/jalview/datamodel/AlignmentI.java
src/jalview/datamodel/BinarySequence.java
src/jalview/datamodel/CigarArray.java
src/jalview/datamodel/ColumnSelection.java
src/jalview/datamodel/HiddenColumns.java
src/jalview/datamodel/HiddenSequences.java
src/jalview/datamodel/SequenceFeature.java
src/jalview/datamodel/SequenceGroup.java
src/jalview/datamodel/VisibleColsIterator.java
src/jalview/gui/AlignFrame.java
src/jalview/gui/AlignViewport.java
src/jalview/gui/AlignmentPanel.java
src/jalview/gui/AnnotationColumnChooser.java
src/jalview/gui/AnnotationLabels.java
src/jalview/gui/AnnotationPanel.java
src/jalview/gui/FeatureColourChooser.java
src/jalview/gui/FeatureRenderer.java
src/jalview/gui/IdCanvas.java
src/jalview/gui/IdPanel.java
src/jalview/gui/Jalview2XML.java
src/jalview/gui/OverviewCanvas.java
src/jalview/gui/OverviewPanel.java
src/jalview/gui/PopupMenu.java
src/jalview/gui/ScalePanel.java
src/jalview/gui/SeqCanvas.java
src/jalview/gui/SeqPanel.java
src/jalview/gui/SliderPanel.java
src/jalview/io/AnnotationFile.java
src/jalview/io/JSONFile.java
src/jalview/renderer/OverviewRenderer.java
src/jalview/renderer/ResidueShader.java
src/jalview/renderer/seqfeatures/FeatureRenderer.java
src/jalview/schemes/FeatureColour.java
src/jalview/schemes/ResidueColourScheme.java
src/jalview/schemes/ScoreColourScheme.java
src/jalview/util/MappingUtils.java
src/jalview/viewmodel/AlignmentViewport.java
src/jalview/viewmodel/OverviewDimensions.java
src/jalview/viewmodel/OverviewDimensionsHideHidden.java
src/jalview/viewmodel/OverviewDimensionsShowHidden.java
src/jalview/viewmodel/ViewportRanges.java
src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java
test/jalview/datamodel/AlignmentTest.java
test/jalview/datamodel/ColumnSelectionTest.java
test/jalview/datamodel/HiddenColumnsTest.java
test/jalview/datamodel/HiddenSequencesTest.java
test/jalview/datamodel/SequenceFeatureTest.java
test/jalview/datamodel/SequenceGroupTest.java
test/jalview/gui/AlignFrameTest.java
test/jalview/gui/AlignViewportTest.java
test/jalview/io/JSONFileTest.java
test/jalview/renderer/seqfeatures/FeatureColourFinderTest.java
test/jalview/renderer/seqfeatures/FeatureRendererTest.java
test/jalview/schemes/BuriedColourSchemeTest.java [new file with mode: 0644]
test/jalview/schemes/FeatureColourTest.java
test/jalview/schemes/HelixColourSchemeTest.java [new file with mode: 0644]
test/jalview/schemes/HydrophobicColourSchemeTest.java [new file with mode: 0644]
test/jalview/schemes/StrandColourSchemeTest.java [new file with mode: 0644]
test/jalview/schemes/TurnColourSchemeTest.java [new file with mode: 0644]
test/jalview/schemes/UserColourSchemeTest.java
test/jalview/util/MappingUtilsTest.java
test/jalview/viewmodel/ViewportRangesTest.java
utils/InstallAnywhere/Jalview.iap_xml

diff --git a/help/html/features/AnnotationColumnSelectionWithSM.png b/help/html/features/AnnotationColumnSelectionWithSM.png
new file mode 100644 (file)
index 0000000..c86c5b8
Binary files /dev/null and b/help/html/features/AnnotationColumnSelectionWithSM.png differ
index b260cee..2651a10 100644 (file)
@@ -37,7 +37,7 @@
   </p>
   <table>
     <tr>
-      <td><img src="AnnotationColumnSelectionWithSM.gif"></td>
+      <td><img src="AnnotationColumnSelectionWithSM.png"></td>
       <td><img src="AnnotationColumnSelectionWithoutSM.gif"></td>
     </tr>
   </table>
@@ -76,7 +76,9 @@
         <li>Select whether to filter the alignment above or below
           the threshold.</li>
         <li>Change the threshold value with the slider, or enter it
-          in the text box.</li>
+          in the text box.
+       <li>The <em>As Percentage</em> checkbox allows thresholds to
+          be set as a percentage rather than absolute value.</li>
       </ul>
     <li><strong>Actions</strong>
       <ul>
index 97d6789..e39a4c1 100755 (executable)
@@ -127,7 +127,7 @@ li:before {
           <ul>
             <li>
               <!-- JAL-2398, -->Fixed incorrect value in BLOSUM 62 score
-              matrix - C->R should be '3'<br />Old matrix restored with
+              matrix - C->R should be '-3'<br />Old matrix restored with
               this one-line groovy script:<br />jalview.analysis.scoremodels.ScoreModels.instance.BLOSUM62.@matrix[4][1]=3
             </li>
             <li>
@@ -136,7 +136,7 @@ li:before {
               earlier versions of Jalview, gaps matching gaps were
               penalised, and gaps matching non-gaps penalised even more.
               In the PCA calculation, gaps were actually treated as
-              non-gaps - so different costs were applied, which mean't
+              non-gaps - so different costs were applied, which meant
               Jalview's PCAs were different to those produced by
               SeqSpace.<br />Jalview now treats gaps in the same way as
               SeqSpace (ie it scores them as 0). To restore pre-2.10.2
index f1ac099..c9d2e63 100644 (file)
@@ -745,7 +745,6 @@ label.delete_sbrs_definition = Delete SBRS Definition
 label.your_sequences_have_been_verified = Your sequences have been verified against known sequence databases.\n(Use Calculate | Show flanking regions to show enclosing sequence.)\nTo preserve data changes, save your alignment.\n\n
 label.sequences_updated = Sequences updated
 label.dbref_search_completed = DBRef search completed
-label.show_all_chains = Show all chains
 label.fetch_all_param = Fetch all {0}
 label.paste_new_window = Paste To New Window
 label.settings_for_param = Settings for {0}
@@ -1282,7 +1281,6 @@ label.SEQUENCE_ID_no_longer_used = $SEQUENCE_ID$ is no longer used for DB access
 label.SEQUENCE_ID_for_DB_ACCESSION1 = Please review your URL links in the 'Connections' tab of the Preferences window:
 label.SEQUENCE_ID_for_DB_ACCESSION2 = URL links using '$SEQUENCE_ID$' for DB accessions now use '$DB_ACCESSION$'.
 label.do_not_display_again = Do not display this message again
-exception.url_cannot_have_miriam_id = {0} is a MIRIAM id and cannot be used as a custom url name
 exception.url_cannot_have_duplicate_id = {0} cannot be used as a label for more than one line
 label.filter = Filter text:
 action.customfilter = Custom only
index 7808480..64f06c8 100644 (file)
@@ -682,7 +682,6 @@ label.delete_sbrs_definition = Borrar una definici
 label.your_sequences_have_been_verified = Sus secuencias has sido verificadas en una base de datos de secuencias conocidas.\n(Usar Calcular | Mostrar flancos para ver ampliación.)\nPara mantener los datos actualizados, guarde su alineamiento.\n\n 
 label.sequences_updated = Secuencias actualizadas
 label.dbref_search_completed = Búsqueda de DBRef terminada
-label.show_all_chains = Mostrar todas las cadenas
 label.fetch_all_param = Recuperar todas {0}
 label.paste_new_window = Pegar en una nueva ventana
 label.settings_for_param = Configuración para {0}
@@ -1282,7 +1281,6 @@ label.SEQUENCE_ID_no_longer_used = $SEQUENCE_ID$ no se utiliza m
 label.SEQUENCE_ID_for_DB_ACCESSION1 = Por favor, revise sus URLs en la pestaña 'Conexiones' de la ventana de Preferencias:
 label.SEQUENCE_ID_for_DB_ACCESSION2 = URL enlaza usando '$SEQUENCE_ID$' para accesiones DB ahora usar '$DB_ACCESSION$'.
 label.do_not_display_again = No mostrar este mensaje de nuevo
-exception.url_cannot_have_miriam_id = {0} es una id MIRIAM y no puede ser usada como nombre url personalizado
 exception.url_cannot_have_duplicate_id = {0} no puede ser usada como etiqueta en más de un enlace
 label.filter = Filtrar texto:
 action.customfilter = Sólo personalizado
@@ -1300,11 +1298,15 @@ warn.name_cannot_be_duplicate = Los nombres URL definidos por el usuario deben s
 label.invalid_name = Nombre inválido !
 label.output_seq_details = Seleccionar Detalles de la secuencia para ver todas
 label.urllinks = Enlaces
+label.default_cache_size = Tamaño del caché por defecto
+action.clear_cached_items = Borrar elementos en caché
 label.quality_descr = Calidad de alineamiento basándose en puntuación Blosum62
 label.conservation_descr = Conservación del alineamiento total menos de {0}% huecos
 label.consensus_descr = % Identidad
 label.complement_consensus_descr = % Identidad para cDNA
 label.strucconsensus_descr = % Identidad para pares de bases
 label.occupancy_descr = Número de posiciones alineadas
-label.togglehidden = Show hidden regions
+label.togglehidden = Mostrar regiones ocultas
+label.show_experimental = Habilitar funciones experimentales
+label.show_experimental_tip = Habilitar funciones nuevas y experimentales (ver Latest Release Notes para más detalles)
 label.warning_hidden = Advertencia: {0} {1} está actualmente oculto
index 01eb7fa..0ded079 100644 (file)
@@ -146,7 +146,9 @@ public interface FeatureColourI
   boolean hasThreshold();
 
   /**
-   * Returns the computed colour for the given sequence feature
+   * Returns the computed colour for the given sequence feature. Answers null if
+   * the score of this feature instance is outside the range to render (if any),
+   * i.e. lies below or above a configured threshold.
    * 
    * @param feature
    * @return
@@ -154,17 +156,6 @@ public interface FeatureColourI
   Color getColor(SequenceFeature feature);
 
   /**
-   * Answers true if the feature has a simple colour, or is coloured by label,
-   * or has a graduated colour and the score of this feature instance is within
-   * the range to render (if any), i.e. does not lie below or above any
-   * threshold set.
-   * 
-   * @param feature
-   * @return
-   */
-  boolean isColored(SequenceFeature feature);
-
-  /**
    * Update the min-max range for a graduated colour scheme
    * 
    * @param min
index d5b7132..80b2d73 100644 (file)
@@ -836,12 +836,16 @@ public class APopupMenu extends java.awt.PopupMenu implements
         }
       }
 
-      if (ap.seqPanel.seqCanvas.getFeatureRenderer().amendFeatures(seqs,
-              features, true, ap))
+      if (!seqs.isEmpty())
       {
-        ap.alignFrame.sequenceFeatures.setState(true);
-        ap.av.setShowSequenceFeatures(true);
-        ap.highlightSearchResults(null);
+        if (ap.seqPanel.seqCanvas.getFeatureRenderer().amendFeatures(seqs,
+                features, true, ap))
+        {
+          ap.alignFrame.sequenceFeatures.setState(true);
+          ap.av.setShowSequenceFeatures(true);
+          ap.av.setSearchResults(null); // clear highlighting
+          ap.repaint(); // draw new/amended features
+        }
       }
     }
     else
index 24f882e..b30c5ae 100644 (file)
@@ -599,25 +599,11 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
     }
 
     case KeyEvent.VK_PAGE_UP:
-      if (viewport.getWrapAlignment())
-      {
-        ranges.scrollUp(true);
-      }
-      else
-      {
-        ranges.pageUp();
-      }
+      ranges.pageUp();
       break;
 
     case KeyEvent.VK_PAGE_DOWN:
-      if (viewport.getWrapAlignment())
-      {
-        ranges.scrollUp(false);
-      }
-      else
-      {
-        ranges.pageDown();
-      }
+      ranges.pageDown();
       break;
 
     case KeyEvent.VK_Z:
@@ -1811,7 +1797,7 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
 
   synchronized void slideSequences(boolean right, int size)
   {
-    List<SequenceI> sg = new Vector<SequenceI>();
+    List<SequenceI> sg = new Vector<>();
     if (viewport.cursorMode)
     {
       sg.add(viewport.getAlignment().getSequenceAt(
@@ -1913,7 +1899,7 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
 
   static StringBuffer copiedSequences;
 
-  static Vector copiedHiddenColumns;
+  static Vector<int[]> copiedHiddenColumns;
 
   protected void copy_actionPerformed()
   {
@@ -1924,7 +1910,7 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
 
     SequenceGroup sg = viewport.getSelectionGroup();
     copiedSequences = new StringBuffer();
-    Map<Integer, SequenceI> orderedSeqs = new HashMap<Integer, SequenceI>();
+    Map<Integer, SequenceI> orderedSeqs = new HashMap<>();
     for (int i = 0; i < sg.getSize(); i++)
     {
       SequenceI seq = sg.getSequenceAt(i);
@@ -1937,13 +1923,13 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
 
     if (viewport.hasHiddenColumns() && viewport.getSelectionGroup() != null)
     {
-      copiedHiddenColumns = new Vector();
+      copiedHiddenColumns = new Vector<>(viewport.getAlignment()
+              .getHiddenColumns().getHiddenColumnsCopy());
       int hiddenOffset = viewport.getSelectionGroup().getStartRes();
-      for (int[] region : viewport.getAlignment().getHiddenColumns()
-              .getHiddenRegions())
+      for (int[] region : copiedHiddenColumns)
       {
-        copiedHiddenColumns.addElement(new int[] {
-            region[0] - hiddenOffset, region[1] - hiddenOffset });
+        region[0] = region[0] - hiddenOffset;
+        region[1] = region[1] - hiddenOffset;
       }
     }
     else
@@ -2058,7 +2044,7 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
         {
           for (int i = 0; i < copiedHiddenColumns.size(); i++)
           {
-            int[] region = (int[]) copiedHiddenColumns.elementAt(i);
+            int[] region = copiedHiddenColumns.elementAt(i);
             af.viewport.hideColumns(region[0], region[1]);
           }
         }
@@ -2644,7 +2630,7 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
     }
 
     Frame frame = new Frame();
-    OverviewPanel overview = new OverviewPanel(alignPanel);
+    final OverviewPanel overview = new OverviewPanel(alignPanel);
     frame.add(overview);
     // +50 must allow for applet frame window
     jalview.bin.JalviewLite.addFrame(frame, MessageManager.formatMessage(
@@ -2659,6 +2645,7 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
       @Override
       public void windowClosing(WindowEvent e)
       {
+        overview.dispose();
         if (ap != null)
         {
           ap.setOverviewPanel(null);
index 73cd9e9..1d2c4bc 100644 (file)
@@ -26,6 +26,7 @@ import jalview.api.FeatureSettingsModelI;
 import jalview.bin.JalviewLite;
 import jalview.commands.CommandI;
 import jalview.datamodel.AlignmentI;
+import jalview.datamodel.Annotation;
 import jalview.datamodel.ColumnSelection;
 import jalview.datamodel.HiddenColumns;
 import jalview.datamodel.SearchResults;
@@ -229,43 +230,6 @@ public class AlignViewport extends AlignmentViewport implements
 
   }
 
-  /**
-   * get the consensus sequence as displayed under the PID consensus annotation
-   * row.
-   * 
-   * @return consensus sequence as a new sequence object
-   */
-  public SequenceI getConsensusSeq()
-  {
-    if (consensus == null)
-    {
-      updateConsensus(null);
-    }
-    if (consensus == null)
-    {
-      return null;
-    }
-    StringBuilder seqs = new StringBuilder(consensus.annotations.length);
-    for (int i = 0; i < consensus.annotations.length; i++)
-    {
-      if (consensus.annotations[i] != null)
-      {
-        if (consensus.annotations[i].description.charAt(0) == '[')
-        {
-          seqs.append(consensus.annotations[i].description.charAt(1));
-        }
-        else
-        {
-          seqs.append(consensus.annotations[i].displayCharacter);
-        }
-      }
-    }
-    SequenceI sq = new Sequence("Consensus", seqs.toString());
-    sq.setDescription("Percentage Identity Consensus "
-            + ((ignoreGapsInConsensusCalculation) ? " without gaps" : ""));
-    return sq;
-  }
-
   java.awt.Frame nullFrame;
 
   protected FeatureSettings featureSettings = null;
index e402b9b..4147177 100644 (file)
@@ -802,42 +802,45 @@ public class AlignmentPanel extends Panel implements AdjustmentListener,
     sendViewPosition();
   }
 
-  private void adjustVertical(int offy)
+  private void adjustVertical(int newY)
   {
-    int oldX = vpRanges.getStartRes();
-    int oldwidth = vpRanges.getViewportWidth();
-    int oldY = vpRanges.getStartSeq();
-    int oldheight = vpRanges.getViewportHeight();
-
     if (av.getWrapAlignment())
     {
-      int rowSize = seqPanel.seqCanvas
-              .getWrappedCanvasWidth(seqPanel.seqCanvas.getWidth());
-
-      // if we're scrolling to the position we're already at, stop
-      // this prevents infinite recursion of events when the scroll/viewport
-      // ranges values are the same
-      if ((offy * rowSize == oldX) && (oldwidth == rowSize))
+      /*
+       * if we're scrolling to the position we're already at, stop
+       * this prevents infinite recursion of events when the scroll/viewport
+       * ranges values are the same
+       */
+      int oldX = vpRanges.getStartRes();
+      int oldY = vpRanges.getWrappedScrollPosition(oldX);
+      if (oldY == newY)
       {
         return;
       }
-      else if (offy > -1)
+      if (newY > -1)
       {
-        vpRanges.setViewportStartAndWidth(offy * rowSize, rowSize);
+        /*
+         * limit page up/down to one width's worth of positions
+         */
+        int rowSize = vpRanges.getViewportWidth();
+        int newX = newY > oldY ? oldX + rowSize : oldX - rowSize;
+        vpRanges.setViewportStartAndWidth(Math.max(0, newX), rowSize);
       }
     }
     else
     {
       int height = seqPanel.seqCanvas.getHeight() / av.getCharHeight();
+      int oldY = vpRanges.getStartSeq();
+      int oldheight = vpRanges.getViewportHeight();
 
       // if we're scrolling to the position we're already at, stop
       // this prevents infinite recursion of events when the scroll/viewport
       // ranges values are the same
-      if ((offy == oldY) && (height == oldheight))
+      if ((newY == oldY) && (height == oldheight))
       {
         return;
       }
-      vpRanges.setViewportStartAndHeight(offy, height);
+      vpRanges.setViewportStartAndHeight(newY, height);
     }
     if (av.getWrapAlignment() || !fastPaint)
     {
@@ -982,35 +985,23 @@ public class AlignmentPanel extends Panel implements AdjustmentListener,
 
   }
 
-  /*
+  /**
    * Set vertical scroll bar parameters for wrapped panel
-   * @param res 
-   *    the residue to scroll to
+   * 
+   * @param topLeftColumn
+   *          the column position at top left (0..)
    */
-  private void setScrollingForWrappedPanel(int res)
+  private void setScrollingForWrappedPanel(int topLeftColumn)
   {
-    // get the width of the alignment in residues
-    int maxwidth = av.getAlignment().getWidth();
-    if (av.hasHiddenColumns())
-    {
-      maxwidth = av.getAlignment().getHiddenColumns()
-              .findColumnPosition(maxwidth) - 1;
-    }
+    int scrollPosition = vpRanges.getWrappedScrollPosition(topLeftColumn);
+    int maxScroll = vpRanges.getWrappedMaxScroll(topLeftColumn);
 
-    // get the width of the canvas in residues
-    int canvasWidth = seqPanel.seqCanvas
-            .getWrappedCanvasWidth(seqPanel.seqCanvas.getSize().width);
-    if (canvasWidth > 0)
-    {
-      // position we want to scroll to is number of canvasWidth's to get there
-      int current = res / canvasWidth;
-
-      // max scroll position: add one because extent is 1 and scrollbar value
-      // can only be set to at most max - extent
-      int max = maxwidth / canvasWidth + 1;
-      vscroll.setUnitIncrement(1);
-      vscroll.setValues(current, 1, 0, max);
-    }
+    /*
+     * a scrollbar's value can be set to at most (maximum-extent)
+     * so we add extent (1) to the maxScroll value
+     */
+    vscroll.setUnitIncrement(1);
+    vscroll.setValues(scrollPosition, 1, 0, maxScroll + 1);
   }
 
   protected Panel sequenceHolderPanel = new Panel();
index 22978c3..3a7188e 100644 (file)
@@ -46,7 +46,7 @@ import java.awt.event.MouseEvent;
 import java.awt.event.MouseListener;
 import java.awt.event.TextEvent;
 import java.awt.event.TextListener;
-import java.util.Iterator;
+import java.util.ArrayList;
 import java.util.Vector;
 
 //import javax.swing.JPanel;
@@ -142,7 +142,7 @@ public class AnnotationColumnChooser extends AnnotationRowFilter implements
     }
     setOldHiddenColumns(av.getAlignment().getHiddenColumns());
     adjusting = true;
-    Vector<String> list = new Vector<String>();
+    Vector<String> list = new Vector<>();
     int index = 1;
     for (int i = 0; i < anns.length; i++)
     {
@@ -298,16 +298,16 @@ public class AnnotationColumnChooser extends AnnotationRowFilter implements
         HiddenColumns oldHidden = av
                 .getAnnotationColumnSelectionState()
                 .getOldHiddenColumns();
-        if (oldHidden != null && oldHidden.getHiddenRegions() != null
-                && !oldHidden.getHiddenRegions().isEmpty())
+        if (oldHidden != null)
         {
-          for (Iterator<int[]> itr = oldHidden.getHiddenRegions()
-                  .iterator(); itr.hasNext();)
+          ArrayList<int[]> regions = oldHidden.getHiddenColumnsCopy();
+          for (int[] positions : regions)
           {
-            int positions[] = itr.next();
             av.hideColumns(positions[0], positions[1]);
           }
         }
+        // TODO not clear why we need to hide all the columns (above) if we are
+        // going to copy the hidden columns over wholesale anyway
         av.getAlignment().setHiddenColumns(oldHidden);
       }
       av.sendSelection();
index 307301d..3f7e523 100755 (executable)
@@ -838,13 +838,9 @@ public class AnnotationLabels extends Panel implements ActionListener,
             + sq.getSequenceAsString() + "\n");
     if (av.hasHiddenColumns())
     {
-      jalview.appletgui.AlignFrame.copiedHiddenColumns = new Vector();
-      for (int[] region : av.getAlignment().getHiddenColumns()
-              .getHiddenRegions())
-      {
-        jalview.appletgui.AlignFrame.copiedHiddenColumns
-                .addElement(new int[] { region[0], region[1] });
-      }
+      jalview.appletgui.AlignFrame.copiedHiddenColumns = new Vector<>(
+              av.getAlignment().getHiddenColumns()
+                      .getHiddenColumnsCopy());
     }
   }
 
index c658811..39b718d 100755 (executable)
@@ -30,6 +30,7 @@ import jalview.util.Comparison;
 import jalview.util.MessageManager;
 import jalview.util.Platform;
 import jalview.viewmodel.ViewportListenerI;
+import jalview.viewmodel.ViewportRanges;
 
 import java.awt.Color;
 import java.awt.Dimension;
@@ -118,13 +119,15 @@ public class AnnotationPanel extends Panel implements AwtRenderPanelI,
 
     // ap.annotationScroller.getVAdjustable().addAdjustmentListener( this );
     renderer = new AnnotationRenderer();
+
+    av.getRanges().addPropertyChangeListener(this);
   }
 
   public AnnotationPanel(AlignViewport av)
   {
     this.av = av;
     renderer = new AnnotationRenderer();
-    av.getRanges().addPropertyChangeListener(this);
+
   }
 
   @Override
@@ -758,8 +761,12 @@ public class AnnotationPanel extends Panel implements AwtRenderPanelI,
   public void propertyChange(PropertyChangeEvent evt)
   {
     // Respond to viewport range changes (e.g. alignment panel was scrolled)
-    if (evt.getPropertyName().equals("startres")
-            || evt.getPropertyName().equals("endres"))
+    // Both scrolling and resizing change viewport ranges: scrolling changes
+    // both start and end points, but resize only changes end values.
+    // Here we only want to fastpaint on a scroll, with resize using a normal
+    // paint, so scroll events are identified as changes to the horizontal or
+    // vertical start value.
+    if (evt.getPropertyName().equals(ViewportRanges.STARTRES))
     {
       fastPaint((int) evt.getNewValue() - (int) evt.getOldValue());
     }
index 0e85017..4075e8b 100644 (file)
@@ -95,9 +95,11 @@ public class FeatureColourChooser extends Panel implements ActionListener,
     float mm[] = fr.getMinMax().get(type)[0];
     min = mm[0];
     max = mm[1];
+    threshline = new GraphLine((max - min) / 2f, "Threshold", Color.black);
     oldcs = fr.getFeatureColours().get(type);
     if (oldcs.isGraduatedColour())
     {
+      threshline.value = oldcs.getThreshold();
       cs = new FeatureColour((FeatureColour) oldcs, min, max);
     }
     else
@@ -384,14 +386,6 @@ public class FeatureColourChooser extends Panel implements ActionListener,
       thresholdValue.setText("");
     }
 
-    else if (aboveThreshold != AnnotationColourGradient.NO_THRESHOLD
-            && threshline == null)
-    {
-      // todo visual indication of feature threshold
-      threshline = new jalview.datamodel.GraphLine((max - min) / 2f,
-              "Threshold", Color.black);
-    }
-
     if (aboveThreshold != AnnotationColourGradient.NO_THRESHOLD)
     {
       adjusting = true;
index 435e78d..8721ff4 100644 (file)
@@ -413,7 +413,8 @@ public class FeatureRenderer extends
         if (!colourPanel.isGcol)
         {
           // update colour - otherwise its already done.
-          setColour(sf.type, new FeatureColour(colourPanel.getBackground()));
+          setColour(enteredType,
+                  new FeatureColour(colourPanel.getBackground()));
         }
         int newBegin = sf.begin;
         int newEnd = sf.end;
@@ -431,14 +432,14 @@ public class FeatureRenderer extends
          * (to ensure integrity of SequenceFeatures data store)
          */
         sequences.get(0).deleteFeature(sf);
-        SequenceFeature newSf = new SequenceFeature(sf, newBegin, newEnd,
-                enteredGroup, sf.getScore());
+        SequenceFeature newSf = new SequenceFeature(sf, enteredType,
+                newBegin, newEnd, enteredGroup, sf.getScore());
         newSf.setDescription(enteredDesc);
         ffile.parseDescriptionHTML(newSf, false);
         // amend features dialog only updates one sequence at a time
         sequences.get(0).addSequenceFeature(newSf);
-        boolean typeOrGroupChanged = (!featureType.equals(sf.type) || !featureGroup
-                .equals(sf.featureGroup));
+        boolean typeOrGroupChanged = (!featureType.equals(newSf.getType()) || !featureGroup
+                .equals(newSf.getFeatureGroup()));
 
         ffile.parseDescriptionHTML(sf, false);
         if (typeOrGroupChanged)
index b0bb372..46498ad 100755 (executable)
@@ -386,11 +386,13 @@ public class FeatureSettings extends Panel implements ItemListener,
       Set<String> visibleGroups = new HashSet<String>();
       for (String group : groups)
       {
-        if (group == null || fr.checkGroupVisibility(group, true))
+        // if (group == null || fr.checkGroupVisibility(group, true))
+        if (group == null || checkGroupState(group))
         {
           visibleGroups.add(group);
         }
       }
+      foundGroups.addAll(groups);
 
       /*
        * get distinct feature types for visible groups
index 74bbcf5..48c0c40 100755 (executable)
@@ -101,7 +101,7 @@ public class IdCanvas extends Panel implements ViewportListenerI
 
   public void fastPaint(int vertical)
   {
-    if (gg == null)
+    if (gg == null || av.getWrapAlignment())
     {
       repaint();
       return;
@@ -199,130 +199,148 @@ public class IdCanvas extends Panel implements ViewportListenerI
 
   void drawIds(int starty, int endy)
   {
-    // hardwired italic IDs in applet currently
-    Font italic = new Font(av.getFont().getName(), Font.ITALIC, av
-            .getFont().getSize());
-    // temp variable for speed
     avcharHeight = av.getCharHeight();
 
-    gg.setFont(italic);
-
     Color currentColor = Color.white;
     Color currentTextColor = Color.black;
 
     final boolean doHiddenCheck = av.isDisplayReferenceSeq()
-            || av.hasHiddenRows(), hiddenRows = av.hasHiddenRows()
-            && av.getShowHiddenMarkers();
+            || av.hasHiddenRows();
+    boolean hiddenRows = av.hasHiddenRows() && av.getShowHiddenMarkers();
 
     if (av.getWrapAlignment())
     {
-      int maxwidth = av.getAlignment().getWidth();
-      int alheight = av.getAlignment().getHeight();
+      drawIdsWrapped(starty, doHiddenCheck, hiddenRows);
+      return;
+    }
 
-      if (av.hasHiddenColumns())
+    // Now draw the id strings
+    SequenceI seq;
+    for (int i = starty; i <= endy; i++)
+    {
+      seq = av.getAlignment().getSequenceAt(i);
+      if (seq == null)
       {
-        maxwidth = av.getAlignment().getHiddenColumns()
-                .findColumnPosition(maxwidth) - 1;
+        continue;
+      }
+      // hardwired italic IDs in applet currently
+      Font italic = new Font(av.getFont().getName(), Font.ITALIC, av
+              .getFont().getSize());
+      gg.setFont(italic);
+      // boolean isrep=false;
+      if (doHiddenCheck)
+      {
+        // isrep =
+        setHiddenFont(seq);
       }
 
-      int annotationHeight = 0;
-      AnnotationLabels labels = null;
-
-      if (av.isShowAnnotation())
+      // Selected sequence colours
+      if ((searchResults != null) && searchResults.contains(seq))
       {
-        AnnotationPanel ap = new AnnotationPanel(av);
-        annotationHeight = ap.adjustPanelHeight();
-        labels = new AnnotationLabels(av);
+        currentColor = Color.black;
+        currentTextColor = Color.white;
       }
-      int hgap = avcharHeight;
-      if (av.getScaleAboveWrapped())
+      else if ((av.getSelectionGroup() != null)
+              && av.getSelectionGroup().getSequences(null).contains(seq))
+      {
+        currentColor = Color.lightGray;
+        currentTextColor = Color.black;
+      }
+      else
       {
-        hgap += avcharHeight;
+        currentColor = av.getSequenceColour(seq);
+        currentTextColor = Color.black;
       }
 
-      int cHeight = alheight * avcharHeight + hgap + annotationHeight;
+      gg.setColor(currentColor);
+      // TODO: isrep could be used to highlight the representative in a
+      // different way
+      gg.fillRect(0, (i - starty) * avcharHeight, getSize().width,
+              avcharHeight);
+      gg.setColor(currentTextColor);
 
-      int rowSize = av.getRanges().getEndRes()
-              - av.getRanges().getStartRes();
-      // Draw the rest of the panels
-      for (int ypos = hgap, row = av.getRanges().getStartRes(); (ypos <= getSize().height)
-              && (row < maxwidth); ypos += cHeight, row += rowSize)
+      gg.drawString(seq.getDisplayId(av.getShowJVSuffix()), 0,
+              (((i - starty) * avcharHeight) + avcharHeight)
+                      - (avcharHeight / 5));
+
+      if (hiddenRows)
       {
-        for (int i = starty; i < alheight; i++)
-        {
+        drawMarker(i, starty, 0);
+      }
+    }
+  }
 
-          SequenceI s = av.getAlignment().getSequenceAt(i);
-          gg.setFont(italic);
-          if (doHiddenCheck)
-          {
-            setHiddenFont(s);
-          }
-          drawIdString(gg, hiddenRows, s, i, 0, ypos);
-        }
+  /**
+   * Draws sequence ids in wrapped mode
+   * 
+   * @param starty
+   * @param doHiddenCheck
+   * @param hiddenRows
+   */
+  protected void drawIdsWrapped(int starty, final boolean doHiddenCheck,
+          boolean hiddenRows)
+  {
+    int maxwidth = av.getAlignment().getWidth();
+    int alheight = av.getAlignment().getHeight();
 
-        if (labels != null)
-        {
-          gg.translate(0, ypos + (alheight * avcharHeight));
-          labels.drawComponent(gg, getSize().width);
-          gg.translate(0, -ypos - (alheight * avcharHeight));
-        }
+    if (av.hasHiddenColumns())
+    {
+      maxwidth = av.getAlignment().getHiddenColumns()
+              .findColumnPosition(maxwidth) - 1;
+    }
 
-      }
+    int annotationHeight = 0;
+    AnnotationLabels labels = null;
+
+    if (av.isShowAnnotation())
+    {
+      AnnotationPanel ap = new AnnotationPanel(av);
+      annotationHeight = ap.adjustPanelHeight();
+      labels = new AnnotationLabels(av);
     }
-    else
+    int hgap = avcharHeight;
+    if (av.getScaleAboveWrapped())
     {
-      // Now draw the id strings
-      SequenceI seq;
-      for (int i = starty; i <= endy; i++)
-      {
+      hgap += avcharHeight;
+    }
 
-        seq = av.getAlignment().getSequenceAt(i);
-        if (seq == null)
-        {
-          continue;
-        }
-        gg.setFont(italic);
-        // boolean isrep=false;
-        if (doHiddenCheck)
-        {
-          // isrep =
-          setHiddenFont(seq);
-        }
+    int cHeight = alheight * avcharHeight + hgap + annotationHeight;
 
-        // Selected sequence colours
-        if ((searchResults != null) && searchResults.contains(seq))
-        {
-          currentColor = Color.black;
-          currentTextColor = Color.white;
-        }
-        else if ((av.getSelectionGroup() != null)
-                && av.getSelectionGroup().getSequences(null).contains(seq))
-        {
-          currentColor = Color.lightGray;
-          currentTextColor = Color.black;
-        }
-        else
-        {
-          currentColor = av.getSequenceColour(seq);
-          currentTextColor = Color.black;
-        }
+    int rowSize = av.getRanges().getViewportWidth();
 
-        gg.setColor(currentColor);
-        // TODO: isrep could be used to highlight the representative in a
-        // different way
-        gg.fillRect(0, (i - starty) * avcharHeight, getSize().width,
-                avcharHeight);
-        gg.setColor(currentTextColor);
+    // hardwired italic IDs in applet currently
+    Font italic = new Font(av.getFont().getName(), Font.ITALIC, av
+            .getFont().getSize());
+    gg.setFont(italic);
 
-        gg.drawString(seq.getDisplayId(av.getShowJVSuffix()), 0,
-                (((i - starty) * avcharHeight) + avcharHeight)
-                        - (avcharHeight / 5));
+    /*
+     * draw repeating sequence ids until out of sequence data or
+     * out of visible space, whichever comes first
+     */
+    int ypos = hgap;
+    int row = av.getRanges().getStartRes();
+    while ((ypos <= getHeight()) && (row < maxwidth))
+    {
+      for (int i = starty; i < alheight; i++)
+      {
 
-        if (hiddenRows)
+        SequenceI s = av.getAlignment().getSequenceAt(i);
+        // gg.setFont(italic);
+        if (doHiddenCheck)
         {
-          drawMarker(i, starty, 0);
+          setHiddenFont(s);
         }
+        drawIdString(gg, hiddenRows, s, i, 0, ypos);
       }
+
+      if (labels != null)
+      {
+        gg.translate(0, ypos + (alheight * avcharHeight));
+        labels.drawComponent(gg, getSize().width);
+        gg.translate(0, -ypos - (alheight * avcharHeight));
+      }
+      ypos += cHeight;
+      row += rowSize;
     }
   }
 
@@ -398,12 +416,25 @@ public class IdCanvas extends Panel implements ViewportListenerI
     return false;
   }
 
+  /**
+   * Respond to viewport range changes (e.g. alignment panel was scrolled). Both
+   * scrolling and resizing change viewport ranges. Scrolling changes both start
+   * and end points, but resize only changes end values. Here we only want to
+   * fastpaint on a scroll, with resize using a normal paint, so scroll events
+   * are identified as changes to the horizontal or vertical start value.
+   * <p>
+   * In unwrapped mode, only responds to a vertical scroll, as horizontal scroll
+   * leaves sequence ids unchanged. In wrapped mode, only vertical scroll is
+   * provided, but it generates a change of "startres" which does require an
+   * update here.
+   */
   @Override
   public void propertyChange(PropertyChangeEvent evt)
   {
-    // Respond to viewport range changes (e.g. alignment panel was scrolled)
-    if (evt.getPropertyName().equals("startseq")
-            || evt.getPropertyName().equals("endseq"))
+    String propertyName = evt.getPropertyName();
+    if (propertyName.equals(ViewportRanges.STARTSEQ)
+            || (av.getWrapAlignment() && propertyName
+                    .equals(ViewportRanges.STARTRES)))
     {
       fastPaint((int) evt.getNewValue() - (int) evt.getOldValue());
     }
index 23e82df..a0466d3 100644 (file)
@@ -40,6 +40,8 @@ public class OverviewCanvas extends Component
 
   private OverviewDimensions od;
 
+  private OverviewRenderer or = null;
+
   private Image miniMe;
 
   private Image offscreen;
@@ -92,6 +94,10 @@ public class OverviewCanvas extends Component
       if (updaterunning)
       {
         restart = true;
+        if (or != null)
+        {
+          or.setRedraw(true);
+        }
       }
       else
       {
@@ -113,7 +119,7 @@ public class OverviewCanvas extends Component
 
     setPreferredSize(new Dimension(od.getWidth(), od.getHeight()));
 
-    OverviewRenderer or = new OverviewRenderer(sr, fr, od);
+    or = new OverviewRenderer(sr, fr, od);
     miniMe = nullFrame.createImage(od.getWidth(), od.getHeight());
     offscreen = nullFrame.createImage(od.getWidth(), od.getHeight());
 
index b3c4a37..ccdfee1 100755 (executable)
@@ -160,6 +160,14 @@ public class OverviewPanel extends Panel implements Runnable,
    */
   public void updateOverviewImage()
   {
+    if (oviewCanvas == null)
+    {
+      /*
+       * panel has been disposed
+       */
+      return;
+    }
+
     if ((getSize().width > 0) && (getSize().height > 0))
     {
       od.setWidth(getSize().width);
@@ -257,4 +265,21 @@ public class OverviewPanel extends Panel implements Runnable,
     oviewCanvas.resetOviewDims(od);
     updateOverviewImage();
   }
+
+  /**
+   * Removes this object as a property change listener, and nulls references
+   */
+  protected void dispose()
+  {
+    try
+    {
+      av.getRanges().removePropertyChangeListener(this);
+    } finally
+    {
+      av = null;
+      oviewCanvas = null;
+      ap = null;
+      od = null;
+    }
+  }
 }
index ec3e246..5e0a2fd 100755 (executable)
@@ -27,6 +27,7 @@ import jalview.renderer.ScaleRenderer;
 import jalview.renderer.ScaleRenderer.ScaleMark;
 import jalview.util.MessageManager;
 import jalview.viewmodel.ViewportListenerI;
+import jalview.viewmodel.ViewportRanges;
 
 import java.awt.Color;
 import java.awt.FontMetrics;
@@ -139,7 +140,7 @@ public class ScalePanel extends Panel implements MouseMotionListener,
       sg.setStartRes(min);
       sg.setEndRes(max);
     }
-    ap.paintAlignment(true);
+    ap.paintAlignment(false);
     av.sendSelection();
   }
 
@@ -165,10 +166,6 @@ public class ScalePanel extends Panel implements MouseMotionListener,
           av.showColumn(reveal[0]);
           reveal = null;
           ap.paintAlignment(true);
-          if (ap.overviewPanel != null)
-          {
-            ap.overviewPanel.updateOverviewImage();
-          }
           av.sendSelection();
         }
       });
@@ -185,10 +182,6 @@ public class ScalePanel extends Panel implements MouseMotionListener,
             av.showAllHiddenColumns();
             reveal = null;
             ap.paintAlignment(true);
-            if (ap.overviewPanel != null)
-            {
-              ap.overviewPanel.updateOverviewImage();
-            }
             av.sendSelection();
           }
         });
@@ -215,10 +208,6 @@ public class ScalePanel extends Panel implements MouseMotionListener,
           }
 
           ap.paintAlignment(true);
-          if (ap.overviewPanel != null)
-          {
-            ap.overviewPanel.updateOverviewImage();
-          }
           av.sendSelection();
         }
       });
@@ -334,18 +323,8 @@ public class ScalePanel extends Panel implements MouseMotionListener,
     int res = (evt.getX() / av.getCharWidth())
             + av.getRanges().getStartRes();
 
-    res = av.getAlignment().getHiddenColumns().adjustForHiddenColumns(res);
-
-    reveal = null;
-    for (int[] region : av.getAlignment().getHiddenColumns()
-            .getHiddenRegions())
-    {
-      if (res + 1 == region[0] || res - 1 == region[1])
-      {
-        reveal = region;
-        break;
-      }
-    }
+    reveal = av.getAlignment().getHiddenColumns()
+            .getRegionWithEdgeAtRes(res);
 
     repaint();
   }
@@ -359,9 +338,15 @@ public class ScalePanel extends Panel implements MouseMotionListener,
   @Override
   public void paint(Graphics g)
   {
-    drawScale(g, av.getRanges().getStartRes(), av.getRanges().getEndRes(),
-            getSize().width,
-            getSize().height);
+    /*
+     * shouldn't get called in wrapped mode as the scale above is
+     * drawn instead by SeqCanvas.drawNorthScale
+     */
+    if (!av.getWrapAlignment())
+    {
+      drawScale(g, av.getRanges().getStartRes(),
+              av.getRanges().getEndRes(), getSize().width, getSize().height);
+    }
   }
 
   // scalewidth will normally be screenwidth,
@@ -450,10 +435,11 @@ public class ScalePanel extends Panel implements MouseMotionListener,
       if (av.getShowHiddenMarkers())
       {
         int widthx = 1 + endx - startx;
-        for (int i = 0; i < hidden.getHiddenRegions().size(); i++)
+        List<Integer> positions = hidden.findHiddenRegionPositions();
+        for (int pos : positions)
         {
 
-          res = hidden.findHiddenRegionPosition(i) - startx;
+          res = pos - startx;
 
           if (res < 0 || res > widthx)
           {
@@ -473,7 +459,16 @@ public class ScalePanel extends Panel implements MouseMotionListener,
   public void propertyChange(PropertyChangeEvent evt)
   {
     // Respond to viewport change events (e.g. alignment panel was scrolled)
-    repaint();
+    // Both scrolling and resizing change viewport ranges: scrolling changes
+    // both start and end points, but resize only changes end values.
+    // Here we only want to fastpaint on a scroll, with resize using a normal
+    // paint, so scroll events are identified as changes to the horizontal or
+    // vertical start value.
+    if (evt.getPropertyName().equals(ViewportRanges.STARTRES))
+    {
+      // scroll event, repaint panel
+      repaint();
+    }
   }
 
 }
index 538f1d2..fe7abbb 100755 (executable)
@@ -37,6 +37,7 @@ import java.awt.Graphics;
 import java.awt.Image;
 import java.awt.Panel;
 import java.beans.PropertyChangeEvent;
+import java.util.List;
 
 public class SeqCanvas extends Panel implements ViewportListenerI
 {
@@ -419,6 +420,9 @@ public class SeqCanvas extends Panel implements ViewportListenerI
 
     FontMetrics fm = getFontMetrics(av.getFont());
 
+    LABEL_EAST = 0;
+    LABEL_WEST = 0;
+
     if (av.getScaleRightWrapped())
     {
       LABEL_EAST = fm.stringWidth(getMask());
@@ -440,17 +444,17 @@ public class SeqCanvas extends Panel implements ViewportListenerI
 
     av.setWrappedWidth(cWidth);
 
-    av.getRanges().setEndRes(av.getRanges().getStartRes() + cWidth - 1);
+    av.getRanges().setViewportStartAndWidth(startRes, cWidth);
 
     int endx;
     int ypos = hgap;
 
-    int maxwidth = av.getAlignment().getWidth() - 1;
+    int maxwidth = av.getAlignment().getWidth();
 
     if (av.hasHiddenColumns())
     {
       maxwidth = av.getAlignment().getHiddenColumns()
-              .findColumnPosition(maxwidth) - 1;
+              .findColumnPosition(maxwidth);
     }
 
     while ((ypos <= canvasHeight) && (startRes < maxwidth))
@@ -487,11 +491,10 @@ public class SeqCanvas extends Panel implements ViewportListenerI
         HiddenColumns hidden = av.getAlignment().getHiddenColumns();
         g.setColor(Color.blue);
         int res;
-        for (int i = 0; i < hidden.getHiddenRegions()
-                .size(); i++)
+        List<Integer> positions = hidden.findHiddenRegionPositions();
+        for (int pos : positions)
         {
-          res = hidden.findHiddenRegionPosition(i)
-                  - startRes;
+          res = pos - startRes;
 
           if (res < 0 || res > endx - startRes)
           {
@@ -570,7 +573,7 @@ public class SeqCanvas extends Panel implements ViewportListenerI
       if (av.hasHiddenColumns())
       {
         HiddenColumns hidden = av.getAlignment().getHiddenColumns();
-        for (int[] region : hidden.getHiddenRegions())
+        for (int[] region : hidden.getHiddenColumnsCopy())
         {
           int hideStart = region[0];
           int hideEnd = region[1];
@@ -882,32 +885,44 @@ public class SeqCanvas extends Panel implements ViewportListenerI
   @Override
   public void propertyChange(PropertyChangeEvent evt)
   {
+    String eventName = evt.getPropertyName();
+
     if (!av.getWrapAlignment())
     {
-      if (evt.getPropertyName().equals("startres")
-              || evt.getPropertyName().equals("endres"))
+      int scrollX = 0;
+      if (eventName.equals(ViewportRanges.STARTRES))
       {
         // Make sure we're not trying to draw a panel
         // larger than the visible window
         ViewportRanges vpRanges = av.getRanges();
-        int scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
-        if (scrollX > vpRanges.getEndRes() - vpRanges.getStartRes())
+        scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
+        int range = vpRanges.getEndRes() - vpRanges.getStartRes();
+        if (scrollX > range)
         {
-          scrollX = vpRanges.getEndRes() - vpRanges.getStartRes();
+          scrollX = range;
         }
-        else if (scrollX < vpRanges.getStartRes() - vpRanges.getEndRes())
+        else if (scrollX < -range)
         {
-          scrollX = vpRanges.getStartRes() - vpRanges.getEndRes();
+          scrollX = -range;
         }
+      }
+
+      // Both scrolling and resizing change viewport ranges: scrolling changes
+      // both start and end points, but resize only changes end values.
+      // Here we only want to fastpaint on a scroll, with resize using a normal
+      // paint, so scroll events are identified as changes to the horizontal or
+      // vertical start value.
+      if (eventName.equals(ViewportRanges.STARTRES))
+      {
+        // scroll - startres and endres both change
         fastPaint(scrollX, 0);
       }
-      else if (evt.getPropertyName().equals("startseq")
-              || evt.getPropertyName().equals("endseq"))
+      else if (eventName.equals(ViewportRanges.STARTSEQ))
       {
+        // scroll
         fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
       }
     }
-
   }
 
 }
index 9e31efe..42e4d8e 100644 (file)
@@ -577,7 +577,8 @@ public class SeqPanel extends Panel implements MouseMotionListener,
         seqCanvas.highlightSearchResults(highlight);
         seqCanvas.getFeatureRenderer().amendFeatures(
                 Collections.singletonList(sequence), features, false, ap);
-        seqCanvas.highlightSearchResults(null);
+        av.setSearchResults(null); // clear highlighting
+        seqCanvas.repaint(); // draw new/amended features
       }
     }
   }
@@ -615,6 +616,7 @@ public class SeqPanel extends Panel implements MouseMotionListener,
     int res = 0;
     int x = evt.getX();
 
+    int startRes = av.getRanges().getStartRes();
     if (av.getWrapAlignment())
     {
 
@@ -629,7 +631,7 @@ public class SeqPanel extends Panel implements MouseMotionListener,
 
       int y = evt.getY();
       y -= hgap;
-      x -= seqCanvas.LABEL_WEST;
+      x = Math.max(0, x - seqCanvas.LABEL_WEST);
 
       int cwidth = seqCanvas.getWrappedCanvasWidth(getSize().width);
       if (cwidth < 1)
@@ -638,14 +640,15 @@ public class SeqPanel extends Panel implements MouseMotionListener,
       }
 
       wrappedBlock = y / cHeight;
-      wrappedBlock += av.getRanges().getStartRes() / cwidth;
-
-      res = wrappedBlock * cwidth + x / av.getCharWidth();
-
+      wrappedBlock += startRes / cwidth;
+      int startOffset = startRes % cwidth; // in case start is scrolled right
+                                           // from 0
+      res = wrappedBlock * cwidth
+              + Math.min(cwidth - 1, startOffset + x / av.getCharWidth());
     }
     else
     {
-      res = (x / av.getCharWidth()) + av.getRanges().getStartRes();
+      res = (x / av.getCharWidth()) + startRes;
     }
 
     if (av.hasHiddenColumns())
@@ -1175,7 +1178,7 @@ public class SeqPanel extends Panel implements MouseMotionListener,
         // Find the next gap before the end
         // of the visible region boundary
         boolean blank = false;
-        for (fixedRight = fixedRight; fixedRight > lastres; fixedRight--)
+        for (; fixedRight > lastres; fixedRight--)
         {
           blank = true;
 
@@ -1871,8 +1874,7 @@ public class SeqPanel extends Panel implements MouseMotionListener,
     }
     if (copycolsel
             && av.hasHiddenColumns()
-            && (av.getColumnSelection() == null || av.getAlignment()
-                    .getHiddenColumns().getHiddenRegions() == null))
+            && (av.getColumnSelection() == null))
     {
       System.err.println("Bad things");
     }
index 9154aa0..47a0669 100644 (file)
@@ -20,6 +20,7 @@
  */
 package jalview.appletgui;
 
+import jalview.analysis.Conservation;
 import jalview.datamodel.SequenceGroup;
 import jalview.renderer.ResidueShaderI;
 import jalview.util.MessageManager;
@@ -44,7 +45,7 @@ import java.awt.event.MouseEvent;
 import java.awt.event.MouseListener;
 import java.awt.event.WindowAdapter;
 import java.awt.event.WindowEvent;
-import java.util.Iterator;
+import java.util.List;
 
 public class SliderPanel extends Panel implements ActionListener,
         AdjustmentListener, MouseListener
@@ -82,7 +83,8 @@ public class SliderPanel extends Panel implements ActionListener,
     conservationSlider.setTitle(MessageManager.formatMessage(
             "label.conservation_colour_increment",
             new String[] { source == null ? BACKGROUND : source }));
-    if (ap.av.getAlignment().getGroups() != null)
+    List<SequenceGroup> groups = ap.av.getAlignment().getGroups();
+    if (groups != null && !groups.isEmpty())
     {
       sp.setAllGroupsCheckEnabled(true);
     }
@@ -248,45 +250,53 @@ public class SliderPanel extends Panel implements ActionListener,
     {
       return;
     }
-
-    ResidueShaderI toChange = cs;
-    Iterator<SequenceGroup> allGroups = null;
-
-    if (allGroupsCheck.getState())
+    if (forConservation)
     {
-      allGroups = ap.av.getAlignment().getGroups().listIterator();
+      cs.setConservationApplied(true);
+      cs.setConservationInc(i);
+    }
+    else
+    {
+      cs.setThreshold(i, ap.av.isIgnoreGapsConsensus());
     }
 
-    while (toChange != null)
+    if (allGroupsCheck.getState())
     {
-      if (forConservation)
+      for (SequenceGroup group : ap.av.getAlignment().getGroups())
       {
-        toChange.setConservationInc(i);
-      }
-      else
-      {
-        toChange.setThreshold(i, ap.av.isIgnoreGapsConsensus());
-      }
-      if (allGroups != null && allGroups.hasNext())
-      {
-        while ((toChange = allGroups.next().cs) == null
-                && allGroups.hasNext())
+        ResidueShaderI groupColourScheme = group.getGroupColourScheme();
+        if (forConservation)
         {
-          ;
+          if (!groupColourScheme.conservationApplied())
+          {
+            /*
+             * first time the colour scheme has had Conservation shading applied
+             * - compute conservation
+             */
+            Conservation c = new Conservation("Group",
+                    group.getSequences(null), group.getStartRes(),
+                    group.getEndRes());
+            c.calculate();
+            c.verdict(false, ap.av.getConsPercGaps());
+            group.cs.setConservation(c);
+
+          }
+          groupColourScheme.setConservationApplied(true);
+          groupColourScheme.setConservationInc(i);
+        }
+        else
+        {
+          groupColourScheme.setThreshold(i, ap.av.isIgnoreGapsConsensus());
         }
-      }
-      else
-      {
-        toChange = null;
       }
     }
 
     ap.seqPanel.seqCanvas.repaint();
-
   }
 
   public void setAllGroupsCheckEnabled(boolean b)
   {
+    allGroupsCheck.setState(ap.av.getColourAppliesToAllGroups());
     allGroupsCheck.setEnabled(b);
   }
 
index 5553840..26e8db1 100755 (executable)
@@ -1909,42 +1909,6 @@ public class Alignment implements AlignmentI
   }
 
   @Override
-  public int[] getVisibleStartAndEndIndex(List<int[]> hiddenCols)
-  {
-    int[] alignmentStartEnd = new int[] { 0, getWidth() - 1 };
-    int startPos = alignmentStartEnd[0];
-    int endPos = alignmentStartEnd[1];
-
-    int[] lowestRange = new int[] { -1, -1 };
-    int[] higestRange = new int[] { -1, -1 };
-
-    for (int[] hiddenCol : hiddenCols)
-    {
-      lowestRange = (hiddenCol[0] <= startPos) ? hiddenCol : lowestRange;
-      higestRange = (hiddenCol[1] >= endPos) ? hiddenCol : higestRange;
-    }
-
-    if (lowestRange[0] == -1 && lowestRange[1] == -1)
-    {
-      startPos = alignmentStartEnd[0];
-    }
-    else
-    {
-      startPos = lowestRange[1] + 1;
-    }
-
-    if (higestRange[0] == -1 && higestRange[1] == -1)
-    {
-      endPos = alignmentStartEnd[1];
-    }
-    else
-    {
-      endPos = higestRange[0] - 1;
-    }
-    return new int[] { startPos, endPos };
-  }
-
-  @Override
   public void setHiddenColumns(HiddenColumns cols)
   {
     hiddenCols = cols;
index 2e61f9d..1b5207f 100755 (executable)
@@ -580,15 +580,6 @@ public interface AlignmentI extends AnnotatedCollectionI
    */
   AlignedCodonFrame getMapping(SequenceI mapFrom, SequenceI mapTo);
 
-  /**
-   * Calculate the visible start and end index of an alignment. The result is
-   * returned an int array where: int[0] = startIndex, and int[1] = endIndex.
-   * 
-   * @param hiddenCols
-   * @return
-   */
-  public int[] getVisibleStartAndEndIndex(List<int[]> hiddenCols);
-
   public void setHiddenColumns(HiddenColumns cols);
 
 }
index 5789b2b..477f4a7 100755 (executable)
@@ -69,6 +69,7 @@ public class BinarySequence extends Sequence
   {
     int nores = (isNa) ? ResidueProperties.maxNucleotideIndex
             : ResidueProperties.maxProteinIndex;
+
     dbinary = new double[getLength() * nores];
 
     return nores;
@@ -131,7 +132,7 @@ public class BinarySequence extends Sequence
   {
     int nores = initMatrixGetNoRes();
 
-    for (int i = 0, iSize = getSequence().length; i < iSize; i++)
+    for (int i = 0, iSize = getLength(); i < iSize; i++)
     {
       int aanum = nores - 1;
 
index 837a10b..febf6b4 100644 (file)
@@ -90,7 +90,7 @@ public class CigarArray extends CigarBase
   {
     this(constructSeqCigarArray(alignment, selectionGroup));
     constructFromAlignment(alignment,
-            hidden != null ? hidden.getHiddenRegions()
+            hidden != null ? hidden.getHiddenColumnsCopy()
                     : null, selectionGroup);
   }
 
index eb2d174..4cdd7af 100644 (file)
@@ -482,7 +482,7 @@ public class ColumnSelection
    */
   public void invertColumnSelection(int first, int width, AlignmentI al)
   {
-    boolean hasHidden = al.getHiddenColumns().hasHidden();
+    boolean hasHidden = al.getHiddenColumns().hasHiddenColumns();
     for (int i = first; i < width; i++)
     {
       if (contains(i))
@@ -511,7 +511,7 @@ public class ColumnSelection
     selection = new IntList();
     if (colsel.selection != null && colsel.selection.size() > 0)
     {
-      if (hiddenColumns.hasHidden())
+      if (hiddenColumns.hasHiddenColumns())
       {
         // only select visible columns in this columns selection
         for (Integer col : colsel.getSelected())
index f0d99e5..169b0a4 100644 (file)
@@ -1,3 +1,23 @@
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License 
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *  
+ * Jalview is distributed in the hope that it will be useful, but 
+ * WITHOUT ANY WARRANTY; without even the implied warranty 
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+ * PURPOSE.  See the GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
 package jalview.datamodel;
 
 import jalview.util.Comparison;
@@ -8,85 +28,159 @@ import java.util.BitSet;
 import java.util.Collections;
 import java.util.List;
 import java.util.Vector;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
 
 public class HiddenColumns
 {
+  private static final ReentrantReadWriteLock LOCK = new ReentrantReadWriteLock();
+  
   /*
    * list of hidden column [start, end] ranges; the list is maintained in
    * ascending start column order
    */
-  private Vector<int[]> hiddenColumns;
+  private ArrayList<int[]> hiddenColumns;
 
   /**
-   * This Method is used to return all the HiddenColumn regions
-   * 
-   * @return empty list or List of hidden column intervals
+   * Constructor
    */
-  public List<int[]> getHiddenRegions()
+  public HiddenColumns()
   {
-    return hiddenColumns == null ? Collections.<int[]> emptyList()
-            : hiddenColumns;
   }
 
   /**
-   * Find the number of hidden columns
+   * Copy constructor
    * 
-   * @return number of hidden columns
+   * @param copy
    */
-  public int getSize()
+  public HiddenColumns(HiddenColumns copy)
   {
-    int size = 0;
-    if (hasHidden())
+    try
     {
-      for (int[] range : hiddenColumns)
+      LOCK.writeLock().lock();
+      if (copy != null)
       {
-        size += range[1] - range[0] + 1;
+        if (copy.hiddenColumns != null)
+        {
+          hiddenColumns = copy.copyHiddenRegionsToArrayList();
+        }
       }
+    } finally
+    {
+      LOCK.writeLock().unlock();
     }
-    return size;
   }
 
   /**
-   * Answers if there are any hidden columns
+   * This method is used to return all the HiddenColumn regions and is intended
+   * to remain private. External callers which need a copy of the regions can
+   * call getHiddenColumnsCopyAsList.
    * 
-   * @return true if there are hidden columns
+   * @return empty list or List of hidden column intervals
    */
-  public boolean hasHidden()
+  private List<int[]> getHiddenRegions()
   {
-    return (hiddenColumns != null) && (!hiddenColumns.isEmpty());
+    return hiddenColumns == null ? Collections.<int[]> emptyList()
+            : hiddenColumns;
   }
 
-  @Override
-  public boolean equals(Object obj)
+  /**
+   * Output regions data as a string. String is in the format:
+   * reg0[0]<between>reg0[1]<delimiter>reg1[0]<between>reg1[1] ... regn[1]
+   * 
+   * @param delimiter
+   *          string to delimit regions
+   * @param betweenstring
+   *          to put between start and end region values
+   * @return regions formatted according to delimiter and between strings
+   */
+  public String regionsToString(String delimiter, String between)
   {
-    if (!(obj instanceof HiddenColumns))
+    try
+    {
+      LOCK.readLock().lock();
+      StringBuilder regionBuilder = new StringBuilder();
+      if (hiddenColumns != null)
+      {
+        for (int[] range : hiddenColumns)
+        {
+          regionBuilder.append(delimiter).append(range[0]).append(between)
+                  .append(range[1]);
+        }
+
+        regionBuilder.deleteCharAt(0);
+      }
+      return regionBuilder.toString();
+    } finally
     {
-      return false;
+      LOCK.readLock().unlock();
     }
-    HiddenColumns that = (HiddenColumns) obj;
+  }
 
-    /*
-     * check hidden columns are either both null, or match
-     */
-    if (this.hiddenColumns == null)
+  /**
+   * Find the number of hidden columns
+   * 
+   * @return number of hidden columns
+   */
+  public int getSize()
+  {
+    try
     {
-      return (that.hiddenColumns == null);
+      LOCK.readLock().lock();
+      int size = 0;
+      if (hasHiddenColumns())
+      {
+        for (int[] range : hiddenColumns)
+        {
+          size += range[1] - range[0] + 1;
+        }
+      }
+      return size;
     }
-    if (that.hiddenColumns == null
-            || that.hiddenColumns.size() != this.hiddenColumns.size())
+    finally
     {
-      return false;
+      LOCK.readLock().unlock();
     }
-    int i = 0;
-    for (int[] thisRange : hiddenColumns)
+  }
+
+  @Override
+  public boolean equals(Object obj)
+  {
+    try
     {
-      int[] thatRange = that.hiddenColumns.get(i++);
-      if (thisRange[0] != thatRange[0] || thisRange[1] != thatRange[1])
+      LOCK.readLock().lock();
+
+      if (!(obj instanceof HiddenColumns))
+      {
+        return false;
+      }
+      HiddenColumns that = (HiddenColumns) obj;
+
+      /*
+       * check hidden columns are either both null, or match
+       */
+      if (this.hiddenColumns == null)
+      {
+        return (that.hiddenColumns == null);
+      }
+      if (that.hiddenColumns == null
+              || that.hiddenColumns.size() != this.hiddenColumns.size())
       {
         return false;
       }
+      int i = 0;
+      for (int[] thisRange : hiddenColumns)
+      {
+        int[] thatRange = that.hiddenColumns.get(i++);
+        if (thisRange[0] != thatRange[0] || thisRange[1] != thatRange[1])
+        {
+          return false;
+        }
+      }
+      return true;
+    } finally
+    {
+      LOCK.readLock().unlock();
     }
-    return true;
   }
 
   /**
@@ -98,19 +192,26 @@ public class HiddenColumns
    */
   public int adjustForHiddenColumns(int column)
   {
-    int result = column;
-    if (hiddenColumns != null)
+    try
     {
-      for (int i = 0; i < hiddenColumns.size(); i++)
+      LOCK.readLock().lock();
+      int result = column;
+      if (hiddenColumns != null)
       {
-        int[] region = hiddenColumns.elementAt(i);
-        if (result >= region[0])
+        for (int i = 0; i < hiddenColumns.size(); i++)
         {
-          result += region[1] - region[0] + 1;
+          int[] region = hiddenColumns.get(i);
+          if (result >= region[0])
+          {
+            result += region[1] - region[0] + 1;
+          }
         }
       }
+      return result;
+    } finally
+    {
+      LOCK.readLock().unlock();
     }
-    return result;
   }
 
   /**
@@ -124,42 +225,52 @@ public class HiddenColumns
    */
   public int findColumnPosition(int hiddenColumn)
   {
-    int result = hiddenColumn;
-    if (hiddenColumns != null)
+    try
     {
-      int index = 0;
-      int[] region;
-      do
+      LOCK.readLock().lock();
+      int result = hiddenColumn;
+      if (hiddenColumns != null)
       {
-        region = hiddenColumns.elementAt(index++);
-        if (hiddenColumn > region[1])
+        int index = 0;
+        int[] region;
+        do
         {
-          result -= region[1] + 1 - region[0];
-        }
-      } while ((hiddenColumn > region[1]) && (index < hiddenColumns.size()));
+          region = hiddenColumns.get(index++);
+          if (hiddenColumn > region[1])
+          {
+            result -= region[1] + 1 - region[0];
+          }
+        } while ((hiddenColumn > region[1])
+                && (index < hiddenColumns.size()));
 
-      if (hiddenColumn >= region[0] && hiddenColumn <= region[1])
-      {
-        // Here the hidden column is within a region, so
-        // we want to return the position of region[0]-1, adjusted for any
-        // earlier hidden columns.
-        // Calculate the difference between the actual hidden col position
-        // and region[0]-1, and then subtract from result to convert result from
-        // the adjusted hiddenColumn value to the adjusted region[0]-1 value
-
-        // However, if the region begins at 0 we cannot return region[0]-1
-        // just return 0
-        if (region[0] == 0)
-        {
-          return 0;
-        }
-        else
+        if (hiddenColumn >= region[0] && hiddenColumn <= region[1])
         {
-          return result - (hiddenColumn - region[0] + 1);
+          // Here the hidden column is within a region, so
+          // we want to return the position of region[0]-1, adjusted for any
+          // earlier hidden columns.
+          // Calculate the difference between the actual hidden col position
+          // and region[0]-1, and then subtract from result to convert result
+          // from
+          // the adjusted hiddenColumn value to the adjusted region[0]-1 value
+
+          // However, if the region begins at 0 we cannot return region[0]-1
+          // just return 0
+          if (region[0] == 0)
+          {
+            return 0;
+          }
+          else
+          {
+            return result - (hiddenColumn - region[0] + 1);
+          }
         }
       }
+      return result; // return the shifted position after removing hidden
+                     // columns.
+    } finally
+    {
+      LOCK.readLock().unlock();
     }
-    return result; // return the shifted position after removing hidden columns.
   }
 
   /**
@@ -175,6 +286,10 @@ public class HiddenColumns
    */
   public int subtractVisibleColumns(int visibleDistance, int startColumn)
   {
+    try
+    {
+
+      LOCK.readLock().lock();
     int distance = visibleDistance;
 
     // in case startColumn is in a hidden region, move it to the left
@@ -216,40 +331,62 @@ public class HiddenColumns
       return nextstart - distance;
     }
     return start - distance;
+    } finally
+    {
+      LOCK.readLock().unlock();
+    }
 
   }
 
   /**
-   * Use this method to determine where the next hiddenRegion starts
+   * Use this method to determine the set of hiddenRegion start positions
    * 
-   * @param hiddenRegion
-   *          index of hidden region (counts from 0)
-   * @return column number in visible view
+   * @return list of column number in visible view where hidden regions start
    */
-  public int findHiddenRegionPosition(int hiddenRegion)
+  public List<Integer> findHiddenRegionPositions()
   {
-    int result = 0;
-    if (hiddenColumns != null)
+    try
     {
-      int index = 0;
-      int gaps = 0;
-      do
+      LOCK.readLock().lock();
+      List<Integer> positions = null;
+
+      if (hiddenColumns != null)
       {
-        int[] region = hiddenColumns.elementAt(index);
-        if (hiddenRegion == 0)
+        positions = new ArrayList<>(hiddenColumns.size());
+
+        positions.add(hiddenColumns.get(0)[0]);
+        for (int i = 1; i < hiddenColumns.size(); ++i)
         {
-          return region[0];
-        }
 
-        gaps += region[1] + 1 - region[0];
-        result = region[1] + 1;
-        index++;
-      } while (index <= hiddenRegion);
+          int result = 0;
+          if (hiddenColumns != null)
+          {
+            int index = 0;
+            int gaps = 0;
+            do
+            {
+              int[] region = hiddenColumns.get(index);
+              gaps += region[1] + 1 - region[0];
+              result = region[1] + 1;
+              index++;
+            } while (index <= i);
 
-      result -= gaps;
-    }
+            result -= gaps;
+          }
+          positions.add(result);
+        }
+      }
+      else
+      {
+        positions = new ArrayList<>();
+      }
 
-    return result;
+      return positions;
+    }
+    finally
+    {
+      LOCK.readLock().unlock();
+    }
   }
 
   /**
@@ -261,22 +398,29 @@ public class HiddenColumns
    */
   public int getHiddenBoundaryRight(int alPos)
   {
-    if (hiddenColumns != null)
+    try
     {
-      int index = 0;
-      do
+      LOCK.readLock().lock();
+      if (hiddenColumns != null)
       {
-        int[] region = hiddenColumns.elementAt(index);
-        if (alPos < region[0])
+        int index = 0;
+        do
         {
-          return region[0];
-        }
+          int[] region = hiddenColumns.get(index);
+          if (alPos < region[0])
+          {
+            return region[0];
+          }
 
-        index++;
-      } while (index < hiddenColumns.size());
-    }
+          index++;
+        } while (index < hiddenColumns.size());
+      }
 
-    return alPos;
+      return alPos;
+    } finally
+    {
+      LOCK.readLock().unlock();
+    }
 
   }
 
@@ -289,12 +433,16 @@ public class HiddenColumns
    */
   public int getHiddenBoundaryLeft(int alPos)
   {
+    try
+    {
+      LOCK.readLock().lock();
+
     if (hiddenColumns != null)
     {
       int index = hiddenColumns.size() - 1;
       do
       {
-        int[] region = hiddenColumns.elementAt(index);
+          int[] region = hiddenColumns.get(index);
         if (alPos > region[1])
         {
           return region[1];
@@ -305,7 +453,10 @@ public class HiddenColumns
     }
 
     return alPos;
-
+    } finally
+    {
+      LOCK.readLock().unlock();
+    }
   }
 
   /**
@@ -318,12 +469,16 @@ public class HiddenColumns
    */
   private int getHiddenIndexLeft(int pos)
   {
+    try
+    {
+
+      LOCK.readLock().lock();
     if (hiddenColumns != null)
     {
       int index = hiddenColumns.size() - 1;
       do
       {
-        int[] region = hiddenColumns.elementAt(index);
+          int[] region = hiddenColumns.get(index);
         if (pos > region[1])
         {
           return index;
@@ -334,6 +489,10 @@ public class HiddenColumns
     }
 
     return -1;
+    } finally
+    {
+      LOCK.readLock().unlock();
+    }
 
   }
 
@@ -345,76 +504,101 @@ public class HiddenColumns
    */
   public void hideColumns(int start, int end)
   {
-    if (hiddenColumns == null)
-    {
-      hiddenColumns = new Vector<int[]>();
-    }
-
-    /*
-     * traverse existing hidden ranges and insert / amend / append as
-     * appropriate
-     */
-    for (int i = 0; i < hiddenColumns.size(); i++)
+    boolean wasAlreadyLocked = false;
+    try
     {
-      int[] region = hiddenColumns.elementAt(i);
-
-      if (end < region[0] - 1)
+      // check if the write lock was already locked by this thread,
+      // as this method can be called internally in loops within HiddenColumns
+      if (!LOCK.isWriteLockedByCurrentThread())
       {
-        /*
-         * insert discontiguous preceding range
-         */
-        hiddenColumns.insertElementAt(new int[] { start, end }, i);
-        return;
+        LOCK.writeLock().lock();
+      }
+      else
+      {
+        wasAlreadyLocked = true;
       }
 
-      if (end <= region[1])
+      if (hiddenColumns == null)
       {
-        /*
-         * new range overlaps existing, or is contiguous preceding it - adjust
-         * start column
-         */
-        region[0] = Math.min(region[0], start);
-        return;
+        hiddenColumns = new ArrayList<>();
       }
 
-      if (start <= region[1] + 1)
+      /*
+       * traverse existing hidden ranges and insert / amend / append as
+       * appropriate
+       */
+      for (int i = 0; i < hiddenColumns.size(); i++)
       {
-        /*
-         * new range overlaps existing, or is contiguous following it - adjust
-         * start and end columns
-         */
-        region[0] = Math.min(region[0], start);
-        region[1] = Math.max(region[1], end);
-
-        /*
-         * also update or remove any subsequent ranges 
-         * that are overlapped
-         */
-        while (i < hiddenColumns.size() - 1)
+        int[] region = hiddenColumns.get(i);
+
+        if (end < region[0] - 1)
+        {
+          /*
+           * insert discontiguous preceding range
+           */
+          hiddenColumns.add(i, new int[] { start, end });
+          return;
+        }
+
+        if (end <= region[1])
+        {
+          /*
+           * new range overlaps existing, or is contiguous preceding it - adjust
+           * start column
+           */
+          region[0] = Math.min(region[0], start);
+          return;
+        }
+
+        if (start <= region[1] + 1)
         {
-          int[] nextRegion = hiddenColumns.get(i + 1);
-          if (nextRegion[0] > end + 1)
+          /*
+           * new range overlaps existing, or is contiguous following it - adjust
+           * start and end columns
+           */
+          region[0] = Math.min(region[0], start);
+          region[1] = Math.max(region[1], end);
+
+          /*
+           * also update or remove any subsequent ranges 
+           * that are overlapped
+           */
+          while (i < hiddenColumns.size() - 1)
           {
-            /*
-             * gap to next hidden range - no more to update
-             */
-            break;
+            int[] nextRegion = hiddenColumns.get(i + 1);
+            if (nextRegion[0] > end + 1)
+            {
+              /*
+               * gap to next hidden range - no more to update
+               */
+              break;
+            }
+            region[1] = Math.max(nextRegion[1], end);
+            hiddenColumns.remove(i + 1);
           }
-          region[1] = Math.max(nextRegion[1], end);
-          hiddenColumns.remove(i + 1);
+          return;
         }
-        return;
-      }
     }
 
     /*
      * remaining case is that the new range follows everything else
      */
-    hiddenColumns.addElement(new int[] { start, end });
+      hiddenColumns.add(new int[] { start, end });
+    } finally
+    {
+      if (!wasAlreadyLocked)
+      {
+        LOCK.writeLock().unlock();
+      }
+    }
   }
 
   public boolean isVisible(int column)
   {
+    try
+    {
+      LOCK.readLock().lock();
+
     if (hiddenColumns != null)
     {
       for (int[] region : hiddenColumns)
@@ -427,39 +611,54 @@ public class HiddenColumns
     }
 
     return true;
+    } finally
+    {
+      LOCK.readLock().unlock();
+    }
   }
 
-  /**
-   * ColumnSelection
-   */
-  public HiddenColumns()
+  private ArrayList<int[]> copyHiddenRegionsToArrayList()
   {
-  }
+    int size = 0;
+    if (hiddenColumns != null)
+    {
+      size = hiddenColumns.size();
+    }
+    ArrayList<int[]> copy = new ArrayList<>(size);
+
+    for (int i = 0, j = size; i < j; i++)
+    {
+      int[] rh;
+      int[] cp;
+      rh = hiddenColumns.get(i);
+      if (rh != null)
+      {
+        cp = new int[rh.length];
+        System.arraycopy(rh, 0, cp, 0, rh.length);
+        copy.add(cp);
+      }
+    }
 
+    return copy;
+  }
+  
   /**
-   * Copy constructor
+   * Returns a copy of the vector of hidden regions, as an ArrayList. Before
+   * using this method please consider if you really need access to the hidden
+   * regions - a new (or existing!) method on HiddenColumns might be more
+   * appropriate.
    * 
-   * @param copy
+   * @return hidden regions as an ArrayList of [start,end] pairs
    */
-  public HiddenColumns(HiddenColumns copy)
+  public ArrayList<int[]> getHiddenColumnsCopy()
   {
-    if (copy != null)
+    try
     {
-      if (copy.hiddenColumns != null)
-      {
-        hiddenColumns = new Vector<int[]>(copy.hiddenColumns.size());
-        for (int i = 0, j = copy.hiddenColumns.size(); i < j; i++)
-        {
-          int[] rh, cp;
-          rh = copy.hiddenColumns.elementAt(i);
-          if (rh != null)
-          {
-            cp = new int[rh.length];
-            System.arraycopy(rh, 0, cp, 0, rh.length);
-            hiddenColumns.addElement(cp);
-          }
-        }
-      }
+      LOCK.readLock().lock();
+      return copyHiddenRegionsToArrayList();
+    } finally
+    {
+      LOCK.readLock().unlock();
     }
   }
 
@@ -474,42 +673,49 @@ public class HiddenColumns
   public List<int[]> compensateForEdit(int start, int change,
           ColumnSelection sel)
   {
-    List<int[]> deletedHiddenColumns = null;
-
-    if (hiddenColumns != null)
+    try
     {
-      deletedHiddenColumns = new ArrayList<int[]>();
-      int hSize = hiddenColumns.size();
-      for (int i = 0; i < hSize; i++)
+      LOCK.writeLock().lock();
+      List<int[]> deletedHiddenColumns = null;
+
+      if (hiddenColumns != null)
       {
-        int[] region = hiddenColumns.elementAt(i);
-        if (region[0] > start && start + change > region[1])
+        deletedHiddenColumns = new ArrayList<>();
+        int hSize = hiddenColumns.size();
+        for (int i = 0; i < hSize; i++)
         {
-          deletedHiddenColumns.add(region);
+          int[] region = hiddenColumns.get(i);
+          if (region[0] > start && start + change > region[1])
+          {
+            deletedHiddenColumns.add(region);
 
-          hiddenColumns.removeElementAt(i);
-          i--;
-          hSize--;
-          continue;
-        }
+            hiddenColumns.remove(i);
+            i--;
+            hSize--;
+            continue;
+          }
 
-        if (region[0] > start)
-        {
-          region[0] -= change;
-          region[1] -= change;
-        }
+          if (region[0] > start)
+          {
+            region[0] -= change;
+            region[1] -= change;
+          }
+
+          if (region[0] < 0)
+          {
+            region[0] = 0;
+          }
 
-        if (region[0] < 0)
-        {
-          region[0] = 0;
         }
 
+        this.revealHiddenColumns(0, sel);
       }
 
-      this.revealHiddenColumns(0, sel);
+      return deletedHiddenColumns;
+    } finally
+    {
+      LOCK.writeLock().unlock();
     }
-
-    return deletedHiddenColumns;
   }
 
   /**
@@ -523,34 +729,42 @@ public class HiddenColumns
    */
   public void compensateForDelEdits(int start, int change)
   {
-    if (hiddenColumns != null)
+    try
     {
-      for (int i = 0; i < hiddenColumns.size(); i++)
+      LOCK.writeLock().lock();
+      if (hiddenColumns != null)
       {
-        int[] region = hiddenColumns.elementAt(i);
-        if (region[0] >= start)
-        {
-          region[0] -= change;
-        }
-        if (region[1] >= start)
-        {
-          region[1] -= change;
-        }
-        if (region[1] < region[0])
+        for (int i = 0; i < hiddenColumns.size(); i++)
         {
-          hiddenColumns.removeElementAt(i--);
-        }
+          int[] region = hiddenColumns.get(i);
+          if (region[0] >= start)
+          {
+            region[0] -= change;
+          }
+          if (region[1] >= start)
+          {
+            region[1] -= change;
+          }
+          if (region[1] < region[0])
+          {
+            hiddenColumns.remove(i--);
+          }
 
-        if (region[0] < 0)
-        {
-          region[0] = 0;
-        }
-        if (region[1] < 0)
-        {
-          region[1] = 0;
+          if (region[0] < 0)
+          {
+            region[0] = 0;
+          }
+          if (region[1] < 0)
+          {
+            region[1] = 0;
+          }
         }
       }
     }
+    finally
+    {
+      LOCK.writeLock().unlock();
+    }
   }
 
   /**
@@ -565,111 +779,130 @@ public class HiddenColumns
    */
   public int[] getVisibleContigs(int start, int end)
   {
-    if (hiddenColumns != null && hiddenColumns.size() > 0)
+    try
     {
-      List<int[]> visiblecontigs = new ArrayList<int[]>();
-      List<int[]> regions = getHiddenRegions();
+      LOCK.readLock().lock();
+      if (hiddenColumns != null && hiddenColumns.size() > 0)
+      {
+        List<int[]> visiblecontigs = new ArrayList<>();
+        List<int[]> regions = getHiddenRegions();
 
-      int vstart = start;
-      int[] region;
-      int hideStart, hideEnd;
+        int vstart = start;
+        int[] region;
+        int hideStart;
+        int hideEnd;
 
-      for (int j = 0; vstart < end && j < regions.size(); j++)
-      {
-        region = regions.get(j);
-        hideStart = region[0];
-        hideEnd = region[1];
+        for (int j = 0; vstart < end && j < regions.size(); j++)
+        {
+          region = regions.get(j);
+          hideStart = region[0];
+          hideEnd = region[1];
+
+          if (hideEnd < vstart)
+          {
+            continue;
+          }
+          if (hideStart > vstart)
+          {
+            visiblecontigs.add(new int[] { vstart, hideStart - 1 });
+          }
+          vstart = hideEnd + 1;
+        }
 
-        if (hideEnd < vstart)
+        if (vstart < end)
         {
-          continue;
+          visiblecontigs.add(new int[] { vstart, end - 1 });
         }
-        if (hideStart > vstart)
+        int[] vcontigs = new int[visiblecontigs.size() * 2];
+        for (int i = 0, j = visiblecontigs.size(); i < j; i++)
         {
-          visiblecontigs.add(new int[] { vstart, hideStart - 1 });
+          int[] vc = visiblecontigs.get(i);
+          visiblecontigs.set(i, null);
+          vcontigs[i * 2] = vc[0];
+          vcontigs[i * 2 + 1] = vc[1];
         }
-        vstart = hideEnd + 1;
-      }
-
-      if (vstart < end)
-      {
-        visiblecontigs.add(new int[] { vstart, end - 1 });
+        visiblecontigs.clear();
+        return vcontigs;
       }
-      int[] vcontigs = new int[visiblecontigs.size() * 2];
-      for (int i = 0, j = visiblecontigs.size(); i < j; i++)
+      else
       {
-        int[] vc = visiblecontigs.get(i);
-        visiblecontigs.set(i, null);
-        vcontigs[i * 2] = vc[0];
-        vcontigs[i * 2 + 1] = vc[1];
+        return new int[] { start, end - 1 };
       }
-      visiblecontigs.clear();
-      return vcontigs;
     }
-    else
+    finally
     {
-      return new int[] { start, end - 1 };
+      LOCK.readLock().unlock();
     }
   }
 
   public String[] getVisibleSequenceStrings(int start, int end,
           SequenceI[] seqs)
   {
-    int i, iSize = seqs.length;
-    String selections[] = new String[iSize];
-    if (hiddenColumns != null && hiddenColumns.size() > 0)
+    try
     {
-      for (i = 0; i < iSize; i++)
+      LOCK.readLock().lock();
+      int iSize = seqs.length;
+      String[] selections = new String[iSize];
+      if (hiddenColumns != null && hiddenColumns.size() > 0)
       {
-        StringBuffer visibleSeq = new StringBuffer();
-        List<int[]> regions = getHiddenRegions();
-
-        int blockStart = start, blockEnd = end;
-        int[] region;
-        int hideStart, hideEnd;
-
-        for (int j = 0; j < regions.size(); j++)
+        for (int i = 0; i < iSize; i++)
         {
-          region = regions.get(j);
-          hideStart = region[0];
-          hideEnd = region[1];
+          StringBuffer visibleSeq = new StringBuffer();
+          List<int[]> regions = getHiddenRegions();
 
-          if (hideStart < start)
+          int blockStart = start;
+          int blockEnd = end;
+          int[] region;
+          int hideStart;
+          int hideEnd;
+
+          for (int j = 0; j < regions.size(); j++)
           {
-            continue;
-          }
+            region = regions.get(j);
+            hideStart = region[0];
+            hideEnd = region[1];
 
-          blockStart = Math.min(blockStart, hideEnd + 1);
-          blockEnd = Math.min(blockEnd, hideStart);
+            if (hideStart < start)
+            {
+              continue;
+            }
 
-          if (blockStart > blockEnd)
-          {
-            break;
+            blockStart = Math.min(blockStart, hideEnd + 1);
+            blockEnd = Math.min(blockEnd, hideStart);
+
+            if (blockStart > blockEnd)
+            {
+              break;
+            }
+
+            visibleSeq.append(seqs[i].getSequence(blockStart, blockEnd));
+
+            blockStart = hideEnd + 1;
+            blockEnd = end;
           }
 
-          visibleSeq.append(seqs[i].getSequence(blockStart, blockEnd));
+          if (end > blockStart)
+          {
+            visibleSeq.append(seqs[i].getSequence(blockStart, end));
+          }
 
-          blockStart = hideEnd + 1;
-          blockEnd = end;
+          selections[i] = visibleSeq.toString();
         }
-
-        if (end > blockStart)
+      }
+      else
+      {
+        for (int i = 0; i < iSize; i++)
         {
-          visibleSeq.append(seqs[i].getSequence(blockStart, end));
+          selections[i] = seqs[i].getSequenceAsString(start, end);
         }
-
-        selections[i] = visibleSeq.toString();
       }
+
+      return selections;
     }
-    else
+    finally
     {
-      for (i = 0; i < iSize; i++)
-      {
-        selections[i] = seqs[i].getSequenceAsString(start, end);
-      }
+      LOCK.readLock().unlock();
     }
-
-    return selections;
   }
 
   /**
@@ -684,70 +917,86 @@ public class HiddenColumns
    */
   public int[] locateVisibleBoundsOfSequence(SequenceI seq)
   {
-    int fpos = seq.getStart(), lpos = seq.getEnd();
-    int start = 0;
-
-    if (hiddenColumns == null || hiddenColumns.size() == 0)
+    try
     {
-      int ifpos = seq.findIndex(fpos) - 1, ilpos = seq.findIndex(lpos) - 1;
-      return new int[] { ifpos, ilpos, fpos, lpos, ifpos, ilpos };
-    }
+      LOCK.readLock().lock();
+      int fpos = seq.getStart();
+      int lpos = seq.getEnd();
+      int start = 0;
 
-    // Simply walk along the sequence whilst watching for hidden column
-    // boundaries
-    List<int[]> regions = getHiddenRegions();
-    int spos = fpos, lastvispos = -1, rcount = 0, hideStart = seq
-            .getLength(), hideEnd = -1;
-    int visPrev = 0, visNext = 0, firstP = -1, lastP = -1;
-    boolean foundStart = false;
-    for (int p = 0, pLen = seq.getLength(); spos <= seq.getEnd()
-            && p < pLen; p++)
-    {
-      if (!Comparison.isGap(seq.getCharAt(p)))
+      if (hiddenColumns == null || hiddenColumns.size() == 0)
       {
-        // keep track of first/last column
-        // containing sequence data regardless of visibility
-        if (firstP == -1)
-        {
-          firstP = p;
-        }
-        lastP = p;
-        // update hidden region start/end
-        while (hideEnd < p && rcount < regions.size())
-        {
-          int[] region = regions.get(rcount++);
-          visPrev = visNext;
-          visNext += region[0] - visPrev;
-          hideStart = region[0];
-          hideEnd = region[1];
-        }
-        if (hideEnd < p)
-        {
-          hideStart = seq.getLength();
-        }
-        // update visible boundary for sequence
-        if (p < hideStart)
+        int ifpos = seq.findIndex(fpos) - 1;
+        int ilpos = seq.findIndex(lpos) - 1;
+        return new int[] { ifpos, ilpos, fpos, lpos, ifpos, ilpos };
+      }
+
+      // Simply walk along the sequence whilst watching for hidden column
+      // boundaries
+      List<int[]> regions = getHiddenRegions();
+      int spos = fpos;
+      int lastvispos = -1;
+      int rcount = 0;
+      int hideStart = seq.getLength();
+      int hideEnd = -1;
+      int visPrev = 0;
+      int visNext = 0;
+      int firstP = -1;
+      int lastP = -1;
+      boolean foundStart = false;
+      for (int p = 0, pLen = seq.getLength(); spos <= seq.getEnd()
+              && p < pLen; p++)
+      {
+        if (!Comparison.isGap(seq.getCharAt(p)))
         {
-          if (!foundStart)
+          // keep track of first/last column
+          // containing sequence data regardless of visibility
+          if (firstP == -1)
+          {
+            firstP = p;
+          }
+          lastP = p;
+          // update hidden region start/end
+          while (hideEnd < p && rcount < regions.size())
+          {
+            int[] region = regions.get(rcount++);
+            visPrev = visNext;
+            visNext += region[0] - visPrev;
+            hideStart = region[0];
+            hideEnd = region[1];
+          }
+          if (hideEnd < p)
           {
-            fpos = spos;
-            start = p;
-            foundStart = true;
+            hideStart = seq.getLength();
           }
-          lastvispos = p;
-          lpos = spos;
+          // update visible boundary for sequence
+          if (p < hideStart)
+          {
+            if (!foundStart)
+            {
+              fpos = spos;
+              start = p;
+              foundStart = true;
+            }
+            lastvispos = p;
+            lpos = spos;
+          }
+          // look for next sequence position
+          spos++;
         }
-        // look for next sequence position
-        spos++;
       }
+      if (foundStart)
+      {
+        return new int[] { findColumnPosition(start),
+            findColumnPosition(lastvispos), fpos, lpos, firstP, lastP };
+      }
+      // otherwise, sequence was completely hidden
+      return new int[] { visPrev, visNext, 0, 0, firstP, lastP };
     }
-    if (foundStart)
+    finally
     {
-      return new int[] { findColumnPosition(start),
-          findColumnPosition(lastvispos), fpos, lpos, firstP, lastP };
+      LOCK.readLock().unlock();
     }
-    // otherwise, sequence was completely hidden
-    return new int[] { visPrev, visNext, 0, 0, firstP, lastP };
   }
 
   /**
@@ -775,88 +1024,100 @@ public class HiddenColumns
   public void makeVisibleAnnotation(int start, int end,
           AlignmentAnnotation alignmentAnnotation)
   {
-    if (alignmentAnnotation.annotations == null)
+    try
     {
-      return;
-    }
-    if (start == end && end == -1)
-    {
-      start = 0;
-      end = alignmentAnnotation.annotations.length;
-    }
-    if (hiddenColumns != null && hiddenColumns.size() > 0)
-    {
-      // then mangle the alignmentAnnotation annotation array
-      Vector<Annotation[]> annels = new Vector<Annotation[]>();
-      Annotation[] els = null;
-      List<int[]> regions = getHiddenRegions();
-      int blockStart = start, blockEnd = end;
-      int[] region;
-      int hideStart, hideEnd, w = 0;
-
-      for (int j = 0; j < regions.size(); j++)
+      LOCK.readLock().lock();
+      if (alignmentAnnotation.annotations == null)
       {
-        region = regions.get(j);
-        hideStart = region[0];
-        hideEnd = region[1];
+        return;
+      }
+      if (start == end && end == -1)
+      {
+        start = 0;
+        end = alignmentAnnotation.annotations.length;
+      }
+      if (hiddenColumns != null && hiddenColumns.size() > 0)
+      {
+        // then mangle the alignmentAnnotation annotation array
+        Vector<Annotation[]> annels = new Vector<>();
+        Annotation[] els = null;
+        List<int[]> regions = getHiddenRegions();
+        int blockStart = start;
+        int blockEnd = end;
+        int[] region;
+        int hideStart;
+        int hideEnd;
+        int w = 0;
 
-        if (hideStart < start)
+        for (int j = 0; j < regions.size(); j++)
         {
-          continue;
-        }
+          region = regions.get(j);
+          hideStart = region[0];
+          hideEnd = region[1];
 
-        blockStart = Math.min(blockStart, hideEnd + 1);
-        blockEnd = Math.min(blockEnd, hideStart);
+          if (hideStart < start)
+          {
+            continue;
+          }
 
-        if (blockStart > blockEnd)
-        {
-          break;
-        }
+          blockStart = Math.min(blockStart, hideEnd + 1);
+          blockEnd = Math.min(blockEnd, hideStart);
 
-        annels.addElement(els = new Annotation[blockEnd - blockStart]);
-        System.arraycopy(alignmentAnnotation.annotations, blockStart, els,
-                0, els.length);
-        w += els.length;
-        blockStart = hideEnd + 1;
-        blockEnd = end;
-      }
+          if (blockStart > blockEnd)
+          {
+            break;
+          }
 
-      if (end > blockStart)
-      {
-        annels.addElement(els = new Annotation[end - blockStart + 1]);
-        if ((els.length + blockStart) <= alignmentAnnotation.annotations.length)
+          annels.addElement(els = new Annotation[blockEnd - blockStart]);
+          System.arraycopy(alignmentAnnotation.annotations, blockStart, els,
+                  0, els.length);
+          w += els.length;
+          blockStart = hideEnd + 1;
+          blockEnd = end;
+        }
+
+        if (end > blockStart)
         {
-          // copy just the visible segment of the annotation row
-          System.arraycopy(alignmentAnnotation.annotations, blockStart,
-                  els, 0, els.length);
+          annels.addElement(els = new Annotation[end - blockStart + 1]);
+          if ((els.length
+                  + blockStart) <= alignmentAnnotation.annotations.length)
+          {
+            // copy just the visible segment of the annotation row
+            System.arraycopy(alignmentAnnotation.annotations, blockStart,
+                    els, 0, els.length);
+          }
+          else
+          {
+            // copy to the end of the annotation row
+            System.arraycopy(alignmentAnnotation.annotations, blockStart,
+                    els, 0,
+                    (alignmentAnnotation.annotations.length - blockStart));
+          }
+          w += els.length;
         }
-        else
+        if (w == 0)
         {
-          // copy to the end of the annotation row
-          System.arraycopy(alignmentAnnotation.annotations, blockStart,
-                  els, 0,
-                  (alignmentAnnotation.annotations.length - blockStart));
+          return;
         }
-        w += els.length;
-      }
-      if (w == 0)
-      {
-        return;
-      }
 
-      alignmentAnnotation.annotations = new Annotation[w];
-      w = 0;
+        alignmentAnnotation.annotations = new Annotation[w];
+        w = 0;
 
-      for (Annotation[] chnk : annels)
+        for (Annotation[] chnk : annels)
+        {
+          System.arraycopy(chnk, 0, alignmentAnnotation.annotations, w,
+                  chnk.length);
+          w += chnk.length;
+        }
+      }
+      else
       {
-        System.arraycopy(chnk, 0, alignmentAnnotation.annotations, w,
-                chnk.length);
-        w += chnk.length;
+        alignmentAnnotation.restrict(start, end);
       }
     }
-    else
+    finally
     {
-      alignmentAnnotation.restrict(start, end);
+      LOCK.readLock().unlock();
     }
   }
 
@@ -866,7 +1127,14 @@ public class HiddenColumns
    */
   public boolean hasHiddenColumns()
   {
-    return hiddenColumns != null && hiddenColumns.size() > 0;
+    try
+    {
+      LOCK.readLock().lock();
+      return hiddenColumns != null && hiddenColumns.size() > 0;
+    } finally
+    {
+      LOCK.readLock().unlock();
+    }
   }
 
   /**
@@ -875,7 +1143,14 @@ public class HiddenColumns
    */
   public boolean hasManyHiddenColumns()
   {
-    return hiddenColumns != null && hiddenColumns.size() > 1;
+    try
+    {
+      LOCK.readLock().lock();
+      return hiddenColumns != null && hiddenColumns.size() > 1;
+    } finally
+    {
+      LOCK.readLock().unlock();
+    }
   }
 
   /**
@@ -886,10 +1161,17 @@ public class HiddenColumns
    */
   public void hideInsertionsFor(SequenceI sr)
   {
-    List<int[]> inserts = sr.getInsertions();
-    for (int[] r : inserts)
+    try
     {
-      hideColumns(r[0], r[1]);
+      LOCK.writeLock().lock();
+      List<int[]> inserts = sr.getInsertions();
+      for (int[] r : inserts)
+      {
+        hideColumns(r[0], r[1]);
+      }
+    } finally
+    {
+      LOCK.writeLock().unlock();
     }
   }
 
@@ -898,19 +1180,27 @@ public class HiddenColumns
    */
   public void revealAllHiddenColumns(ColumnSelection sel)
   {
-    if (hiddenColumns != null)
+    try
     {
-      for (int i = 0; i < hiddenColumns.size(); i++)
+      LOCK.writeLock().lock();
+      if (hiddenColumns != null)
       {
-        int[] region = hiddenColumns.elementAt(i);
-        for (int j = region[0]; j < region[1] + 1; j++)
+        for (int i = 0; i < hiddenColumns.size(); i++)
         {
-          sel.addElement(j);
+          int[] region = hiddenColumns.get(i);
+          for (int j = region[0]; j < region[1] + 1; j++)
+          {
+            sel.addElement(j);
+          }
         }
       }
-    }
 
-    hiddenColumns = null;
+      hiddenColumns = null;
+    }
+    finally
+    {
+      LOCK.writeLock().unlock();
+    }
   }
 
   /**
@@ -921,23 +1211,31 @@ public class HiddenColumns
    */
   public void revealHiddenColumns(int start, ColumnSelection sel)
   {
-    for (int i = 0; i < hiddenColumns.size(); i++)
+    try
     {
-      int[] region = hiddenColumns.elementAt(i);
-      if (start == region[0])
+      LOCK.writeLock().lock();
+      for (int i = 0; i < hiddenColumns.size(); i++)
       {
-        for (int j = region[0]; j < region[1] + 1; j++)
+        int[] region = hiddenColumns.get(i);
+        if (start == region[0])
         {
-          sel.addElement(j);
-        }
+          for (int j = region[0]; j < region[1] + 1; j++)
+          {
+            sel.addElement(j);
+          }
 
-        hiddenColumns.removeElement(region);
-        break;
+          hiddenColumns.remove(region);
+          break;
+        }
+      }
+      if (hiddenColumns.size() == 0)
+      {
+        hiddenColumns = null;
       }
     }
-    if (hiddenColumns.size() == 0)
+    finally
     {
-      hiddenColumns = null;
+      LOCK.writeLock().unlock();
     }
   }
 
@@ -949,13 +1247,16 @@ public class HiddenColumns
    * @param intervals
    * @return
    */
-  private boolean pruneIntervalVector(final List<int[]> shifts,
-          Vector<int[]> intervals)
+  private boolean pruneIntervalList(final List<int[]> shifts,
+          ArrayList<int[]> intervals)
   {
     boolean pruned = false;
-    int i = 0, j = intervals.size() - 1, s = 0, t = shifts.size() - 1;
-    int hr[] = intervals.elementAt(i);
-    int sr[] = shifts.get(s);
+    int i = 0;
+    int j = intervals.size() - 1;
+    int s = 0;
+    int t = shifts.size() - 1;
+    int[] hr = intervals.get(i);
+    int[] sr = shifts.get(s);
     while (i <= j && s <= t)
     {
       boolean trailinghn = hr[1] >= sr[0];
@@ -963,7 +1264,7 @@ public class HiddenColumns
       {
         if (i < j)
         {
-          hr = intervals.elementAt(++i);
+          hr = intervals.get(++i);
         }
         else
         {
@@ -991,12 +1292,12 @@ public class HiddenColumns
       {
         if (trailinghc)
         { // deleted hidden region.
-          intervals.removeElementAt(i);
+          intervals.remove(i);
           pruned = true;
           j--;
           if (i <= j)
           {
-            hr = intervals.elementAt(i);
+            hr = intervals.get(i);
           }
           continue;
         }
@@ -1044,15 +1345,23 @@ public class HiddenColumns
    */
   public void pruneDeletions(List<int[]> shifts)
   {
-    // delete any intervals intersecting.
-    if (hiddenColumns != null)
+    try
     {
-      pruneIntervalVector(shifts, hiddenColumns);
-      if (hiddenColumns != null && hiddenColumns.size() == 0)
+      LOCK.writeLock().lock();
+      // delete any intervals intersecting.
+      if (hiddenColumns != null)
       {
-        hiddenColumns = null;
+        pruneIntervalList(shifts, hiddenColumns);
+        if (hiddenColumns != null && hiddenColumns.size() == 0)
+        {
+          hiddenColumns = null;
+        }
       }
     }
+    finally
+    {
+      LOCK.writeLock().unlock();
+    }
   }
 
   /**
@@ -1098,8 +1407,7 @@ public class HiddenColumns
     // recover mapping between sequence's non-gap positions and positions
     // mapping to view.
     pruneDeletions(ShiftList.parseMap(origseq.gapMap()));
-    int[] viscontigs = al.getHiddenColumns().getVisibleContigs(0,
-            profileseq.getLength());
+    int[] viscontigs = getVisibleContigs(0, profileseq.getLength());
     int spos = 0;
     int offset = 0;
 
@@ -1251,16 +1559,24 @@ public class HiddenColumns
   @Override
   public int hashCode()
   {
-    int hashCode = 1;
-    if (hiddenColumns != null)
+    try
     {
-      for (int[] hidden : hiddenColumns)
+      LOCK.readLock().lock();
+      int hashCode = 1;
+      if (hiddenColumns != null)
       {
-        hashCode = 31 * hashCode + hidden[0];
-        hashCode = 31 * hashCode + hidden[1];
+        for (int[] hidden : hiddenColumns)
+        {
+          hashCode = 31 * hashCode + hidden[0];
+          hashCode = 31 * hashCode + hidden[1];
+        }
       }
+      return hashCode;
+    }
+    finally
+    {
+      LOCK.readLock().unlock();
     }
-    return hashCode;
   }
 
   /**
@@ -1271,11 +1587,19 @@ public class HiddenColumns
    */
   public void hideMarkedBits(BitSet inserts)
   {
-    for (int firstSet = inserts.nextSetBit(0), lastSet = 0; firstSet >= 0; firstSet = inserts
-            .nextSetBit(lastSet))
+    try
     {
-      lastSet = inserts.nextClearBit(firstSet);
-      hideColumns(firstSet, lastSet - 1);
+      LOCK.writeLock().lock();
+      for (int firstSet = inserts
+              .nextSetBit(0), lastSet = 0; firstSet >= 0; firstSet = inserts
+                      .nextSetBit(lastSet))
+      {
+        lastSet = inserts.nextClearBit(firstSet);
+        hideColumns(firstSet, lastSet - 1);
+      }
+    } finally
+    {
+      LOCK.writeLock().unlock();
     }
   }
 
@@ -1286,13 +1610,109 @@ public class HiddenColumns
    */
   public void markHiddenRegions(BitSet inserts)
   {
-    if (hiddenColumns == null)
+    try
+    {
+      LOCK.readLock().lock();
+      if (hiddenColumns == null)
+      {
+        return;
+      }
+      for (int[] range : hiddenColumns)
+      {
+        inserts.set(range[0], range[1] + 1);
+      }
+    }
+    finally
     {
-      return;
+      LOCK.readLock().unlock();
     }
-    for (int[] range : hiddenColumns)
+  }
+
+  /**
+   * Calculate the visible start and end index of an alignment.
+   * 
+   * @param width
+   *          full alignment width
+   * @return integer array where: int[0] = startIndex, and int[1] = endIndex
+   */
+  public int[] getVisibleStartAndEndIndex(int width)
+  {
+    try
+    {
+      LOCK.readLock().lock();
+      int[] alignmentStartEnd = new int[] { 0, width - 1 };
+      int startPos = alignmentStartEnd[0];
+      int endPos = alignmentStartEnd[1];
+
+      int[] lowestRange = new int[] { -1, -1 };
+      int[] higestRange = new int[] { -1, -1 };
+
+      if (hiddenColumns == null)
+      {
+        return new int[] { startPos, endPos };
+      }
+
+      for (int[] hiddenCol : hiddenColumns)
+      {
+        lowestRange = (hiddenCol[0] <= startPos) ? hiddenCol : lowestRange;
+        higestRange = (hiddenCol[1] >= endPos) ? hiddenCol : higestRange;
+      }
+
+      if (lowestRange[0] == -1 && lowestRange[1] == -1)
+      {
+        startPos = alignmentStartEnd[0];
+      }
+      else
+      {
+        startPos = lowestRange[1] + 1;
+      }
+
+      if (higestRange[0] == -1 && higestRange[1] == -1)
+      {
+        endPos = alignmentStartEnd[1];
+      }
+      else
+      {
+        endPos = higestRange[0] - 1;
+      }
+      return new int[] { startPos, endPos };
+    } finally
+    {
+      LOCK.readLock().unlock();
+    }
+
+  }
+
+  /**
+   * Finds the hidden region (if any) which starts or ends at res
+   * 
+   * @param res
+   *          visible residue position, unadjusted for hidden columns
+   * @return region as [start,end] or null if no matching region is found
+   */
+  public int[] getRegionWithEdgeAtRes(int res)
+  {
+    try
+    {
+      LOCK.readLock().lock();
+      int adjres = adjustForHiddenColumns(res);
+
+      int[] reveal = null;
+      if (hiddenColumns != null)
+      {
+        for (int[] region : hiddenColumns)
+        {
+          if (adjres + 1 == region[0] || adjres - 1 == region[1])
+          {
+            reveal = region;
+            break;
+          }
+        }
+      }
+      return reveal;
+    } finally
     {
-      inserts.set(range[0], range[1] + 1);
+      LOCK.readLock().unlock();
     }
   }
 
index a98b10e..98e9694 100755 (executable)
@@ -157,9 +157,10 @@ public class HiddenSequences
     int absAlignmentIndex = alignment.findIndex(sequence);
     int alignmentIndex = adjustForHiddenSeqs(absAlignmentIndex);
 
-    if (hiddenSequences[alignmentIndex] != null)
+    if (alignmentIndex < 0 || hiddenSequences[alignmentIndex] != null)
     {
       System.out.println("ERROR!!!!!!!!!!!");
+      return;
     }
 
     hiddenSequences[alignmentIndex] = sequence;
@@ -253,7 +254,8 @@ public class HiddenSequences
   }
 
   /**
-   * Convert absolute alignment index to visible alignment index
+   * Convert absolute alignment index to visible alignment index (or -1 if
+   * before the first visible sequence)
    * 
    * @param alignmentIndex
    * @return
index 9a6cf2b..ba7412c 100755 (executable)
@@ -143,15 +143,16 @@ public class SequenceFeature implements FeatureLocationI
    * A copy constructor that allows the value of final fields to be 'modified'
    * 
    * @param sf
+   * @param newType
    * @param newBegin
    * @param newEnd
    * @param newGroup
    * @param newScore
    */
-  public SequenceFeature(SequenceFeature sf, int newBegin, int newEnd,
-          String newGroup, float newScore)
+  public SequenceFeature(SequenceFeature sf, String newType, int newBegin,
+          int newEnd, String newGroup, float newScore)
   {
-    this(sf.getType(), sf.getDescription(), newBegin, newEnd, newScore,
+    this(newType, sf.getDescription(), newBegin, newEnd, newScore,
             newGroup);
 
     if (sf.otherDetails != null)
@@ -173,6 +174,21 @@ public class SequenceFeature implements FeatureLocationI
   }
 
   /**
+   * A copy constructor that allows the value of final fields to be 'modified'
+   * 
+   * @param sf
+   * @param newBegin
+   * @param newEnd
+   * @param newGroup
+   * @param newScore
+   */
+  public SequenceFeature(SequenceFeature sf, int newBegin, int newEnd,
+          String newGroup, float newScore)
+  {
+    this(sf, sf.getType(), newBegin, newEnd, newGroup, newScore);
+  }
+
+  /**
    * Two features are considered equal if they have the same type, group,
    * description, start, end, phase, strand, and (if present) 'Name', ID' and
    * 'Parent' attributes.
index 463b909..46c802f 100755 (executable)
@@ -188,7 +188,7 @@ public class SequenceGroup implements AnnotatedCollectionI
       colourText = seqsel.colourText;
       startRes = seqsel.startRes;
       endRes = seqsel.endRes;
-      cs = new ResidueShader(seqsel.getColourScheme());
+      cs = new ResidueShader((ResidueShader) seqsel.cs);
       if (seqsel.description != null)
       {
         description = new String(seqsel.description);
index 70de1e3..a82de93 100644 (file)
@@ -48,7 +48,7 @@ public class VisibleColsIterator implements Iterator<Integer>
     last = lastcol;
     current = firstcol;
     next = firstcol;
-    hidden = hiddenCols.getHiddenRegions();
+    hidden = hiddenCols.getHiddenColumnsCopy();
     lasthiddenregion = -1;
 
     if (hidden != null)
index a9a970f..e5d550f 100644 (file)
@@ -167,7 +167,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
 
   public AlignViewControllerI avc;
 
-  List<AlignmentPanel> alignPanels = new ArrayList<AlignmentPanel>();
+  List<AlignmentPanel> alignPanels = new ArrayList<>();
 
   /**
    * Last format used to load or save alignments in this window
@@ -396,8 +396,8 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
 
     addKeyListener();
 
-    final List<AlignmentPanel> selviews = new ArrayList<AlignmentPanel>();
-    final List<AlignmentPanel> origview = new ArrayList<AlignmentPanel>();
+    final List<AlignmentPanel> selviews = new ArrayList<>();
+    final List<AlignmentPanel> origview = new ArrayList<>();
     final String menuLabel = MessageManager
             .getString("label.copy_format_from");
     ViewSelectionMenu vsel = new ViewSelectionMenu(menuLabel,
@@ -410,7 +410,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
                 origview.clear();
                 origview.add(alignPanel);
                 // make an array of all alignment panels except for this one
-                List<AlignmentPanel> aps = new ArrayList<AlignmentPanel>(
+                List<AlignmentPanel> aps = new ArrayList<>(
                         Arrays.asList(Desktop.getAlignmentPanels(null)));
                 aps.remove(AlignFrame.this.alignPanel);
                 return aps.toArray(new AlignmentPanel[aps.size()]);
@@ -689,24 +689,10 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
           break;
         }
         case KeyEvent.VK_PAGE_UP:
-          if (viewport.getWrapAlignment())
-          {
-            vpRanges.scrollUp(true);
-          }
-          else
-          {
-            vpRanges.pageUp();
-          }
+          vpRanges.pageUp();
           break;
         case KeyEvent.VK_PAGE_DOWN:
-          if (viewport.getWrapAlignment())
-          {
-            vpRanges.scrollUp(false);
-          }
-          else
-          {
-            vpRanges.pageDown();
-          }
+          vpRanges.pageDown();
           break;
         }
       }
@@ -1320,10 +1306,8 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     {
       alignmentToExport = viewport.getAlignment();
     }
-    alignmentStartEnd = alignmentToExport
-            .getVisibleStartAndEndIndex(viewport.getAlignment()
-                    .getHiddenColumns()
-                    .getHiddenRegions());
+    alignmentStartEnd = viewport.getAlignment().getHiddenColumns()
+            .getVisibleStartAndEndIndex(alignmentToExport.getWidth());
     AlignmentExportData ed = new AlignmentExportData(alignmentToExport,
             omitHidden, alignmentStartEnd, settings);
     return ed;
@@ -1732,7 +1716,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
 
   synchronized void slideSequences(boolean right, int size)
   {
-    List<SequenceI> sg = new ArrayList<SequenceI>();
+    List<SequenceI> sg = new ArrayList<>();
     if (viewport.cursorMode)
     {
       sg.add(viewport.getAlignment().getSequenceAt(
@@ -1751,7 +1735,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
       return;
     }
 
-    List<SequenceI> invertGroup = new ArrayList<SequenceI>();
+    List<SequenceI> invertGroup = new ArrayList<>();
 
     for (SequenceI seq : viewport.getAlignment().getSequences())
     {
@@ -1884,11 +1868,12 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     ArrayList<int[]> hiddenColumns = null;
     if (viewport.hasHiddenColumns())
     {
-      hiddenColumns = new ArrayList<int[]>();
-      int hiddenOffset = viewport.getSelectionGroup().getStartRes(), hiddenCutoff = viewport
-              .getSelectionGroup().getEndRes();
-      for (int[] region : viewport.getAlignment().getHiddenColumns()
-              .getHiddenRegions())
+      hiddenColumns = new ArrayList<>();
+      int hiddenOffset = viewport.getSelectionGroup().getStartRes();
+      int hiddenCutoff = viewport.getSelectionGroup().getEndRes();
+      ArrayList<int[]> hiddenRegions = viewport.getAlignment()
+              .getHiddenColumns().getHiddenColumnsCopy();
+      for (int[] region : hiddenRegions)
       {
         if (region[0] >= hiddenOffset && region[1] <= hiddenCutoff)
         {
@@ -1994,7 +1979,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
       }
 
       int alwidth = 0;
-      ArrayList<Integer> newGraphGroups = new ArrayList<Integer>();
+      ArrayList<Integer> newGraphGroups = new ArrayList<>();
       int fgroup = -1;
 
       if (newAlignment)
@@ -2457,7 +2442,6 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     viewport.setSelectionGroup(null);
     viewport.getColumnSelection().clear();
     viewport.setSelectionGroup(null);
-    alignPanel.getSeqPanel().seqCanvas.highlightSearchResults(null);
     alignPanel.getIdPanel().getIdCanvas().searchResults = null;
     // JAL-2034 - should delegate to
     // alignPanel to decide if overview needs
@@ -2826,7 +2810,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    */
   protected List<String> getExistingViewNames(List<Component> comps)
   {
-    List<String> existingNames = new ArrayList<String>();
+    List<String> existingNames = new ArrayList<>();
     for (Component comp : comps)
     {
       if (comp instanceof AlignmentPanel)
@@ -3215,10 +3199,6 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   {
     viewport.setShowSequenceFeatures(showSeqFeatures.isSelected());
     alignPanel.paintAlignment(true);
-    if (alignPanel.getOverviewPanel() != null)
-    {
-      alignPanel.getOverviewPanel().updateOverviewImage();
-    }
   }
 
   /**
@@ -3274,7 +3254,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     }
 
     JInternalFrame frame = new JInternalFrame();
-    OverviewPanel overview = new OverviewPanel(alignPanel);
+    final OverviewPanel overview = new OverviewPanel(alignPanel);
     frame.setContentPane(overview);
     Desktop.addInternalFrame(frame, MessageManager.formatMessage(
             "label.overview_params", new Object[] { this.getTitle() }),
@@ -3287,6 +3267,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
       public void internalFrameClosed(
               javax.swing.event.InternalFrameEvent evt)
       {
+        overview.dispose();
         alignPanel.setOverviewPanel(null);
       };
     });
@@ -3760,7 +3741,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
 
     List<Component> comps = PaintRefresher.components.get(viewport
             .getSequenceSetId());
-    List<TreePanel> treePanels = new ArrayList<TreePanel>();
+    List<TreePanel> treePanels = new ArrayList<>();
     for (Component comp : comps)
     {
       if (comp instanceof TreePanel)
@@ -4020,7 +4001,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
       @Override
       public void run()
       {
-        final List<JMenuItem> legacyItems = new ArrayList<JMenuItem>();
+        final List<JMenuItem> legacyItems = new ArrayList<>();
         try
         {
           // System.err.println("Building ws menu again "
@@ -4035,7 +4016,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
           // TODO: group services by location as well as function and/or
           // introduce
           // object broker mechanism.
-          final Vector<JMenu> wsmenu = new Vector<JMenu>();
+          final Vector<JMenu> wsmenu = new Vector<>();
           final IProgressIndicator af = me;
 
           /*
@@ -4403,8 +4384,8 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     // Java's Transferable for native dnd
     evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
     Transferable t = evt.getTransferable();
-    List<String> files = new ArrayList<String>();
-    List<DataSourceType> protocols = new ArrayList<DataSourceType>();
+    List<String> files = new ArrayList<>();
+    List<DataSourceType> protocols = new ArrayList<>();
 
     try
     {
@@ -4424,8 +4405,8 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
         /**
          * Object[] { String,SequenceI}
          */
-        ArrayList<Object[]> filesmatched = new ArrayList<Object[]>();
-        ArrayList<String> filesnotmatched = new ArrayList<String>();
+        ArrayList<Object[]> filesmatched = new ArrayList<>();
+        ArrayList<String> filesnotmatched = new ArrayList<>();
         for (int i = 0; i < files.size(); i++)
         {
           String file = files.get(i).toString();
@@ -5416,7 +5397,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     {
       return;
     }
-    List<SequenceI> cdnaSeqs = new ArrayList<SequenceI>();
+    List<SequenceI> cdnaSeqs = new ArrayList<>();
     for (SequenceI aaSeq : alignment.getSequences())
     {
       for (AlignedCodonFrame acf : mappings)
index 86e1144..835371f 100644 (file)
@@ -39,7 +39,6 @@ import jalview.datamodel.HiddenColumns;
 import jalview.datamodel.PDBEntry;
 import jalview.datamodel.SearchResults;
 import jalview.datamodel.SearchResultsI;
-import jalview.datamodel.Sequence;
 import jalview.datamodel.SequenceGroup;
 import jalview.datamodel.SequenceI;
 import jalview.renderer.ResidueShader;
@@ -311,44 +310,6 @@ public class AlignViewport extends AlignmentViewport implements
     }
   }
 
-  /**
-   * get the consensus sequence as displayed under the PID consensus annotation
-   * row.
-   * 
-   * @return consensus sequence as a new sequence object
-   */
-  public SequenceI getConsensusSeq()
-  {
-    if (consensus == null)
-    {
-      updateConsensus(null);
-    }
-    if (consensus == null)
-    {
-      return null;
-    }
-    StringBuffer seqs = new StringBuffer();
-    for (int i = 0; i < consensus.annotations.length; i++)
-    {
-      if (consensus.annotations[i] != null)
-      {
-        if (consensus.annotations[i].description.charAt(0) == '[')
-        {
-          seqs.append(consensus.annotations[i].description.charAt(1));
-        }
-        else
-        {
-          seqs.append(consensus.annotations[i].displayCharacter);
-        }
-      }
-    }
-
-    SequenceI sq = new Sequence("Consensus", seqs.toString());
-    sq.setDescription("Percentage Identity Consensus "
-            + ((ignoreGapsInConsensusCalculation) ? " without gaps" : ""));
-    return sq;
-  }
-
   boolean validCharWidth;
 
   /**
index 72b1cc9..fc687b4 100644 (file)
@@ -343,19 +343,11 @@ public class AlignmentPanel extends GAlignmentPanel implements
    */
   public void highlightSearchResults(SearchResultsI results)
   {
-    scrollToPosition(results);
-    getSeqPanel().seqCanvas.highlightSearchResults(results);
-  }
+    boolean scrolled = scrollToPosition(results, 0, true, false);
 
-  /**
-   * Scroll the view to show the position of the highlighted region in results
-   * (if any) and redraw the overview
-   * 
-   * @param results
-   */
-  public boolean scrollToPosition(SearchResultsI results)
-  {
-    return scrollToPosition(results, 0, true, false);
+    boolean noFastPaint = scrolled && av.getWrapAlignment();
+
+    getSeqPanel().seqCanvas.highlightSearchResults(results, noFastPaint);
   }
 
   /**
@@ -373,8 +365,10 @@ public class AlignmentPanel extends GAlignmentPanel implements
   }
 
   /**
-   * Scroll the view to show the position of the highlighted region in results
-   * (if any)
+   * Scrolls the view (if necessary) to show the position of the first
+   * highlighted region in results (if any). Answers true if the view was
+   * scrolled, or false if no matched region was found, or it is already
+   * visible.
    * 
    * @param results
    * @param verticalOffset
@@ -384,116 +378,117 @@ public class AlignmentPanel extends GAlignmentPanel implements
    *          - when set, the overview will be recalculated (takes longer)
    * @param centre
    *          if true, try to centre the search results horizontally in the view
-   * @return false if results were not found
+   * @return
    */
-  public boolean scrollToPosition(SearchResultsI results,
+  protected boolean scrollToPosition(SearchResultsI results,
           int verticalOffset, boolean redrawOverview, boolean centre)
   {
     int startv, endv, starts, ends;
-    // TODO: properly locate search results in view when large numbers of hidden
-    // columns exist before highlighted region
-    // do we need to scroll the panel?
-    // TODO: tons of nullpointerexceptions raised here.
-    if (results != null && results.getSize() > 0 && av != null
-            && av.getAlignment() != null)
-    {
-      int seqIndex = av.getAlignment().findIndex(results);
-      if (seqIndex == -1)
-      {
-        return false;
-      }
-      SequenceI seq = av.getAlignment().getSequenceAt(seqIndex);
 
-      int[] r = results.getResults(seq, 0, av.getAlignment().getWidth());
-      if (r == null)
-      {
-        return false;
-      }
-      int start = r[0];
-      int end = r[1];
+    if (results == null || results.isEmpty() || av == null
+            || av.getAlignment() == null)
+    {
+      return false;
+    }
+    int seqIndex = av.getAlignment().findIndex(results);
+    if (seqIndex == -1)
+    {
+      return false;
+    }
+    SequenceI seq = av.getAlignment().getSequenceAt(seqIndex);
 
-      /*
-       * To centre results, scroll to positions half the visible width
-       * left/right of the start/end positions
-       */
-      if (centre)
-      {
-        int offset = (vpRanges.getEndRes() - vpRanges.getStartRes() + 1) / 2
-                - 1;
-        start = Math.max(start - offset, 0);
-        end = end + offset - 1;
-      }
-      if (start < 0)
-      {
-        return false;
-      }
-      if (end == seq.getEnd())
-      {
-        return false;
-      }
-      if (av.hasHiddenColumns())
+    int[] r = results.getResults(seq, 0, av.getAlignment().getWidth());
+    if (r == null)
+    {
+      return false;
+    }
+    int start = r[0];
+    int end = r[1];
+
+    /*
+     * To centre results, scroll to positions half the visible width
+     * left/right of the start/end positions
+     */
+    if (centre)
+    {
+      int offset = (vpRanges.getEndRes() - vpRanges.getStartRes() + 1) / 2 - 1;
+      start = Math.max(start - offset, 0);
+      end = end + offset - 1;
+    }
+    if (start < 0)
+    {
+      return false;
+    }
+    if (end == seq.getEnd())
+    {
+      return false;
+    }
+
+    if (av.hasHiddenColumns())
+    {
+      HiddenColumns hidden = av.getAlignment().getHiddenColumns();
+      start = hidden.findColumnPosition(start);
+      end = hidden.findColumnPosition(end);
+      if (start == end)
       {
-        HiddenColumns hidden = av.getAlignment().getHiddenColumns();
-        start = hidden.findColumnPosition(start);
-        end = hidden.findColumnPosition(end);
-        if (start == end)
+        if (!hidden.isVisible(r[0]))
         {
-          if (!hidden.isVisible(r[0]))
-          {
-            // don't scroll - position isn't visible
-            return false;
-          }
+          // don't scroll - position isn't visible
+          return false;
         }
       }
+    }
 
-      /*
-       * allow for offset of target sequence (actually scroll to one above it)
-       */
-      seqIndex = Math.max(0, seqIndex - verticalOffset);
+    /*
+     * allow for offset of target sequence (actually scroll to one above it)
+     */
+    seqIndex = Math.max(0, seqIndex - verticalOffset);
+    boolean scrollNeeded = true;
 
-      if (!av.getWrapAlignment())
+    if (!av.getWrapAlignment())
+    {
+      if ((startv = vpRanges.getStartRes()) >= start)
       {
-        if ((startv = vpRanges.getStartRes()) >= start)
-        {
-          /*
-           * Scroll left to make start of search results visible
-           */
-          setScrollValues(start, seqIndex);
-        }
-        else if ((endv = vpRanges.getEndRes()) <= end)
-        {
-          /*
-           * Scroll right to make end of search results visible
-           */
-          setScrollValues(startv + end - endv, seqIndex);
-        }
-        else if ((starts = vpRanges.getStartSeq()) > seqIndex)
-        {
-          /*
-           * Scroll up to make start of search results visible
-           */
-          setScrollValues(vpRanges.getStartRes(), seqIndex);
-        }
-        else if ((ends = vpRanges.getEndSeq()) <= seqIndex)
-        {
-          /*
-           * Scroll down to make end of search results visible
-           */
-          setScrollValues(vpRanges.getStartRes(), starts + seqIndex - ends
-                  + 1);
-        }
         /*
-         * Else results are already visible - no need to scroll
+         * Scroll left to make start of search results visible
          */
+        setScrollValues(start, seqIndex);
       }
-      else
+      else if ((endv = vpRanges.getEndRes()) <= end)
+      {
+        /*
+         * Scroll right to make end of search results visible
+         */
+        setScrollValues(startv + end - endv, seqIndex);
+      }
+      else if ((starts = vpRanges.getStartSeq()) > seqIndex)
+      {
+        /*
+         * Scroll up to make start of search results visible
+         */
+        setScrollValues(vpRanges.getStartRes(), seqIndex);
+      }
+      else if ((ends = vpRanges.getEndSeq()) <= seqIndex)
       {
-        vpRanges.scrollToWrappedVisible(start);
+        /*
+         * Scroll down to make end of search results visible
+         */
+        setScrollValues(vpRanges.getStartRes(), starts + seqIndex - ends
+                + 1);
       }
+      /*
+       * Else results are already visible - no need to scroll
+       */
+      scrollNeeded = false;
+    }
+    else
+    {
+      scrollNeeded = vpRanges.scrollToWrappedVisible(start);
     }
 
     paintAlignment(redrawOverview);
-    return true;
+
+    return scrollNeeded;
   }
 
   /**
@@ -631,21 +626,24 @@ public class AlignmentPanel extends GAlignmentPanel implements
       annotationSpaceFillerHolder.setVisible(true);
     }
 
-    if (wrap)
-    {
-      int widthInRes = getSeqPanel().seqCanvas
-              .getWrappedCanvasWidth(getSeqPanel().seqCanvas.getWidth());
-      vpRanges.setViewportWidth(widthInRes);
-    }
-    else
-    {
-      int widthInRes = (getSeqPanel().seqCanvas.getWidth() / av
-              .getCharWidth()) - 1;
-      int heightInSeq = (getSeqPanel().seqCanvas.getHeight() / av
-              .getCharHeight()) - 1;
+    int canvasWidth = getSeqPanel().seqCanvas.getWidth();
+    if (canvasWidth > 0)
+    { // may not yet be laid out
+      if (wrap)
+      {
+        int widthInRes = getSeqPanel().seqCanvas
+                .getWrappedCanvasWidth(canvasWidth);
+        vpRanges.setViewportWidth(widthInRes);
+      }
+      else
+      {
+        int widthInRes = (canvasWidth / av.getCharWidth()) - 1;
+        int heightInSeq = (getSeqPanel().seqCanvas.getHeight() / av
+                .getCharHeight()) - 1;
 
-      vpRanges.setViewportWidth(widthInRes);
-      vpRanges.setViewportHeight(heightInSeq);
+        vpRanges.setViewportWidth(widthInRes);
+        vpRanges.setViewportHeight(heightInSeq);
+      }
     }
 
     idSpaceFillerPanel1.setVisible(!wrap);
@@ -737,97 +735,111 @@ public class AlignmentPanel extends GAlignmentPanel implements
   @Override
   public void adjustmentValueChanged(AdjustmentEvent evt)
   {
-    int oldX = vpRanges.getStartRes();
-    int oldwidth = vpRanges.getViewportWidth();
-    int oldY = vpRanges.getStartSeq();
-    int oldheight = vpRanges.getViewportHeight();
-
     if (av.getWrapAlignment())
     {
-      if (evt.getSource() == hscroll)
-      {
-        return; // no horizontal scroll when wrapped
-      }
-      else if (evt.getSource() == vscroll)
+      adjustScrollingWrapped(evt);
+      return;
+    }
+
+    if (evt.getSource() == hscroll)
+    {
+      int oldX = vpRanges.getStartRes();
+      int oldwidth = vpRanges.getViewportWidth();
+      int x = hscroll.getValue();
+      int width = getSeqPanel().seqCanvas.getWidth() / av.getCharWidth();
+
+      // if we're scrolling to the position we're already at, stop
+      // this prevents infinite recursion of events when the scroll/viewport
+      // ranges values are the same
+      if ((x == oldX) && (width == oldwidth))
       {
-        int offy = vscroll.getValue();
-        int rowSize = getSeqPanel().seqCanvas
-                .getWrappedCanvasWidth(getSeqPanel().seqCanvas.getWidth());
-
-        // if we're scrolling to the position we're already at, stop
-        // this prevents infinite recursion of events when the scroll/viewport
-        // ranges values are the same
-        if ((offy * rowSize == oldX) && (oldwidth == rowSize))
-        {
-          return;
-        }
-        else if (offy > -1)
-        {
-          vpRanges.setViewportStartAndWidth(offy * rowSize, rowSize);
-        }
+        return;
       }
-      else
+      vpRanges.setViewportStartAndWidth(x, width);
+    }
+    else if (evt.getSource() == vscroll)
+    {
+      int oldY = vpRanges.getStartSeq();
+      int oldheight = vpRanges.getViewportHeight();
+      int y = vscroll.getValue();
+      int height = getSeqPanel().seqCanvas.getHeight() / av.getCharHeight();
+
+      // if we're scrolling to the position we're already at, stop
+      // this prevents infinite recursion of events when the scroll/viewport
+      // ranges values are the same
+      if ((y == oldY) && (height == oldheight))
       {
-        // This is only called if file loaded is a jar file that
-        // was wrapped when saved and user has wrap alignment true
-        // as preference setting
-        SwingUtilities.invokeLater(new Runnable()
-        {
-          @Override
-          public void run()
-        {
-            // When updating scrolling to use ViewportChange events, this code
-            // could not be validated and it is not clear if it is now being
-            // called. Log warning here in case it is called and unforeseen
-            // problems occur
-            Cache.log
-                    .warn("Unexpected path through code: Wrapped jar file opened with wrap alignment set in preferences");
-
-            // scroll to start of panel
-            vpRanges.setStartRes(0);
-            vpRanges.setStartSeq(0);
-          }
-        });
+        return;
       }
+      vpRanges.setViewportStartAndHeight(y, height);
+    }
+    if (!fastPaint)
+    {
       repaint();
     }
-    else
+  }
+
+  /**
+   * Responds to a scroll change by setting the start position of the viewport.
+   * Does
+   * 
+   * @param evt
+   */
+  protected void adjustScrollingWrapped(AdjustmentEvent evt)
+  {
+    if (evt.getSource() == hscroll)
     {
-      // horizontal scroll
-      if (evt.getSource() == hscroll)
-      {
-        int x = hscroll.getValue();
-        int width = getSeqPanel().seqCanvas.getWidth() / av.getCharWidth();
+      return; // no horizontal scroll when wrapped
+    }
+    if (evt.getSource() == vscroll)
+    {
+      int newY = vscroll.getValue();
 
-        // if we're scrolling to the position we're already at, stop
-        // this prevents infinite recursion of events when the scroll/viewport
-        // ranges values are the same
-        if ((x == oldX) && (width == oldwidth))
-        {
-          return;
-        }
-        vpRanges.setViewportStartAndWidth(x, width);
-      }
-      else if (evt.getSource() == vscroll)
+      /*
+       * if we're scrolling to the position we're already at, stop
+       * this prevents infinite recursion of events when the scroll/viewport
+       * ranges values are the same
+       */
+      int oldX = vpRanges.getStartRes();
+      int oldY = vpRanges.getWrappedScrollPosition(oldX);
+      if (oldY == newY)
       {
-        int y = vscroll.getValue();
-        int height = getSeqPanel().seqCanvas.getHeight()
-                / av.getCharHeight();
-
-        // if we're scrolling to the position we're already at, stop
-        // this prevents infinite recursion of events when the scroll/viewport
-        // ranges values are the same
-        if ((y == oldY) && (height == oldheight))
-        {
-          return;
-        }
-        vpRanges.setViewportStartAndHeight(y, height);
+        return;
       }
-      if (!fastPaint)
+      if (newY > -1)
       {
-        repaint();
+        /*
+         * limit page up/down to one width's worth of positions
+         */
+        int rowSize = vpRanges.getViewportWidth();
+        int newX = newY > oldY ? oldX + rowSize : oldX - rowSize;
+        vpRanges.setViewportStartAndWidth(Math.max(0, newX), rowSize);
       }
     }
+    else
+    {
+      // This is only called if file loaded is a jar file that
+      // was wrapped when saved and user has wrap alignment true
+      // as preference setting
+      SwingUtilities.invokeLater(new Runnable()
+      {
+        @Override
+        public void run()
+      {
+          // When updating scrolling to use ViewportChange events, this code
+          // could not be validated and it is not clear if it is now being
+          // called. Log warning here in case it is called and unforeseen
+          // problems occur
+          Cache.log
+                  .warn("Unexpected path through code: Wrapped jar file opened with wrap alignment set in preferences");
+
+          // scroll to start of panel
+          vpRanges.setStartRes(0);
+          vpRanges.setStartSeq(0);
+        }
+      });
+    }
+    repaint();
   }
 
   /**
@@ -877,35 +889,24 @@ public class AlignmentPanel extends GAlignmentPanel implements
     setScrollValues(vpRanges.getStartRes(), vpRanges.getStartSeq());
   }
 
-  /*
-   * Set vertical scroll bar parameters for wrapped panel
-   * @param res 
-   *    the residue to scroll to
+  /**
+   * Set vertical scroll bar position, and number of increments, for wrapped
+   * panel
+   * 
+   * @param topLeftColumn
+   *          the column position at top left (0..)
    */
-  private void setScrollingForWrappedPanel(int res)
+  private void setScrollingForWrappedPanel(int topLeftColumn)
   {
-    // get the width of the alignment in residues
-    int maxwidth = av.getAlignment().getWidth();
-    if (av.hasHiddenColumns())
-    {
-        maxwidth = av.getAlignment().getHiddenColumns()
-                .findColumnPosition(maxwidth) - 1;
-    }
+    int scrollPosition = vpRanges.getWrappedScrollPosition(topLeftColumn);
+    int maxScroll = vpRanges.getWrappedMaxScroll(topLeftColumn);
 
-    // get the width of the canvas in residues
-    int canvasWidth = getSeqPanel().seqCanvas
-            .getWrappedCanvasWidth(getSeqPanel().seqCanvas.getWidth());
-    if (canvasWidth > 0)
-    {
-      // position we want to scroll to is number of canvasWidth's to get there
-      int current = res / canvasWidth;
-
-      // max scroll position: add one because extent is 1 and scrollbar value
-      // can only be set to at most max - extent
-      int max = maxwidth / canvasWidth + 1;
-      vscroll.setUnitIncrement(1);
-      vscroll.setValues(current, 1, 0, max);
-    }
+    /*
+     * a scrollbar's value can be set to at most (maximum-extent)
+     * so we add extent (1) to the maxScroll value
+     */
+    vscroll.setUnitIncrement(1);
+    vscroll.setValues(scrollPosition, 1, 0, maxScroll + 1);
   }
 
   /**
@@ -1809,7 +1810,7 @@ public class AlignmentPanel extends GAlignmentPanel implements
    * @param verticalOffset
    *          the number of visible sequences to show above the mapped region
    */
-  public void scrollToCentre(SearchResultsI sr, int verticalOffset)
+  protected void scrollToCentre(SearchResultsI sr, int verticalOffset)
   {
     /*
      * To avoid jumpy vertical scrolling (if some sequences are gapped or not
index f81455e..6fc5fad 100644 (file)
@@ -36,7 +36,7 @@ import java.awt.event.ActionListener;
 import java.awt.event.ItemEvent;
 import java.awt.event.ItemListener;
 import java.awt.event.KeyEvent;
-import java.util.Iterator;
+import java.util.ArrayList;
 
 import javax.swing.ButtonGroup;
 import javax.swing.JCheckBox;
@@ -244,16 +244,16 @@ public class AnnotationColumnChooser extends AnnotationRowFilter implements
         HiddenColumns oldHidden = av
                 .getAnnotationColumnSelectionState()
                 .getOldHiddenColumns();
-        if (oldHidden != null && oldHidden.getHiddenRegions() != null
-                && !oldHidden.getHiddenRegions().isEmpty())
+        if (oldHidden != null)
         {
-          for (Iterator<int[]> itr = oldHidden.getHiddenRegions()
-                  .iterator(); itr.hasNext();)
+          ArrayList<int[]> regions = oldHidden.getHiddenColumnsCopy();
+          for (int[] positions : regions)
           {
-            int positions[] = itr.next();
             av.hideColumns(positions[0], positions[1]);
           }
         }
+        // TODO not clear why we need to hide all the columns (above) if we are
+        // going to copy the hidden columns over wholesale anyway
         av.getAlignment().setHiddenColumns(oldHidden);
       }
       av.sendSelection();
@@ -728,7 +728,7 @@ public class AnnotationColumnChooser extends AnnotationRowFilter implements
 
     private static final String FILTER_BY_ANN_CACHE_KEY = "CACHE.SELECT_FILTER_BY_ANNOT";
 
-    public JvCacheableInputBox<String> searchBox = new JvCacheableInputBox<String>(
+    public JvCacheableInputBox<String> searchBox = new JvCacheableInputBox<>(
             FILTER_BY_ANN_CACHE_KEY);
 
     public SearchPanel(AnnotationColumnChooser aColChooser)
index 8ca1a4e..e16867a 100755 (executable)
@@ -52,7 +52,6 @@ import java.awt.image.BufferedImage;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
-import java.util.List;
 import java.util.regex.Pattern;
 
 import javax.swing.JCheckBoxMenuItem;
@@ -953,13 +952,12 @@ public class AnnotationLabels extends JPanel implements MouseListener,
     }
 
     int[] alignmentStartEnd = new int[] { 0, ds.getWidth() - 1 };
-    List<int[]> hiddenCols = av.getAlignment().getHiddenColumns()
-            .getHiddenRegions();
-    if (hiddenCols != null)
+    if (av.hasHiddenColumns())
     {
-      alignmentStartEnd = av.getAlignment().getVisibleStartAndEndIndex(
-              hiddenCols);
+      alignmentStartEnd = av.getAlignment().getHiddenColumns()
+            .getVisibleStartAndEndIndex(av.getAlignment().getWidth());
     }
+
     String output = new FormatAdapter().formatSequences(FileFormat.Fasta,
             seqs, omitHidden, alignmentStartEnd);
 
@@ -967,14 +965,11 @@ public class AnnotationLabels extends JPanel implements MouseListener,
             .setContents(new StringSelection(output), Desktop.instance);
 
     ArrayList<int[]> hiddenColumns = null;
+
     if (av.hasHiddenColumns())
     {
-      hiddenColumns = new ArrayList<int[]>();
-      for (int[] region : av.getAlignment().getHiddenColumns()
-              .getHiddenRegions())
-      {
-        hiddenColumns.add(new int[] { region[0], region[1] });
-      }
+      hiddenColumns = av.getAlignment().getHiddenColumns()
+              .getHiddenColumnsCopy();
     }
 
     Desktop.jalviewClipboard = new Object[] { seqs, ds, // what is the dataset
index 452f002..61099c3 100755 (executable)
@@ -31,6 +31,7 @@ import jalview.schemes.ResidueProperties;
 import jalview.util.Comparison;
 import jalview.util.MessageManager;
 import jalview.viewmodel.ViewportListenerI;
+import jalview.viewmodel.ViewportRanges;
 
 import java.awt.AlphaComposite;
 import java.awt.Color;
@@ -451,7 +452,7 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI,
      * the selection list (read-only view) is in selection order, not
      * column order; make a copy so we can sort it
      */
-    List<Integer> selected = new ArrayList<Integer>(viscols.getSelected());
+    List<Integer> selected = new ArrayList<>(viscols.getSelected());
     Collections.sort(selected);
     for (int index : selected)
     {
@@ -981,13 +982,15 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI,
       repaint();
       return;
     }
+
+    int sr = av.getRanges().getStartRes();
+    int er = av.getRanges().getEndRes() + 1;
+    int transX = 0;
+
     long stime = System.currentTimeMillis();
     gg.copyArea(0, 0, imgWidth, getHeight(),
             -horizontal * av.getCharWidth(), 0);
     long mtime = System.currentTimeMillis();
-    int sr = av.getRanges().getStartRes();
-    int er = av.getRanges().getEndRes() + 1;
-    int transX = 0;
 
     if (horizontal > 0) // scrollbar pulled right, image to the left
     {
@@ -1168,8 +1171,12 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI,
   public void propertyChange(PropertyChangeEvent evt)
   {
     // Respond to viewport range changes (e.g. alignment panel was scrolled)
-    if (evt.getPropertyName().equals("startres")
-            || evt.getPropertyName().equals("endres"))
+    // Both scrolling and resizing change viewport ranges: scrolling changes
+    // both start and end points, but resize only changes end values.
+    // Here we only want to fastpaint on a scroll, with resize using a normal
+    // paint, so scroll events are identified as changes to the horizontal or
+    // vertical start value.
+    if (evt.getPropertyName().equals(ViewportRanges.STARTRES))
     {
       fastPaint((int) evt.getNewValue() - (int) evt.getOldValue());
     }
index 5594e1a..a1c1bff 100644 (file)
@@ -178,9 +178,10 @@ public class FeatureColourChooser extends JalviewDialog
       // initialise threshold slider and selector
       threshold.setSelectedIndex(cs.isAboveThreshold() ? 1 : 2);
       slider.setEnabled(true);
+      slider.setValue((int) (cs.getThreshold() * scaleFactor));
       thresholdValue.setEnabled(true);
       threshline = new GraphLine((max - min) / 2f, "Threshold", Color.black);
-
+      threshline.value = cs.getThreshold();
     }
 
     adjusting = false;
index ac56590..6f6bc02 100644 (file)
@@ -271,7 +271,8 @@ public class FeatureRenderer extends
             highlight.addResult(sequences.get(0), sf.getBegin(),
                     sf.getEnd());
 
-            alignPanel.getSeqPanel().seqCanvas.highlightSearchResults(highlight);
+            alignPanel.getSeqPanel().seqCanvas.highlightSearchResults(
+                    highlight, false);
 
           }
           FeatureColourI col = getFeatureStyle(name.getText());
@@ -448,8 +449,8 @@ public class FeatureRenderer extends
          * (to ensure integrity of SequenceFeatures data store)
          */
         sequences.get(0).deleteFeature(sf);
-        SequenceFeature newSf = new SequenceFeature(sf, newBegin, newEnd,
-                enteredGroup, sf.getScore());
+        SequenceFeature newSf = new SequenceFeature(sf, enteredType,
+                newBegin, newEnd, enteredGroup, sf.getScore());
         sf.setDescription(enteredDescription);
         ffile.parseDescriptionHTML(newSf, false);
         // amend features dialog only updates one sequence at a time
index 5ce36cb..052c527 100755 (executable)
@@ -155,7 +155,10 @@ public class IdCanvas extends JPanel implements ViewportListenerI
    */
   public void fastPaint(int vertical)
   {
-    if (gg == null)
+    /*
+     * for now, not attempting fast paint of wrapped ids...
+     */
+    if (gg == null || av.getWrapAlignment())
     {
       repaint();
 
@@ -283,143 +286,158 @@ public class IdCanvas extends JPanel implements ViewportListenerI
     Color currentColor = Color.white;
     Color currentTextColor = Color.black;
 
-    final boolean doHiddenCheck = av.isDisplayReferenceSeq()
-            || av.hasHiddenRows(), hiddenRows = av.hasHiddenRows();
+    boolean hasHiddenRows = av.hasHiddenRows();
 
     if (av.getWrapAlignment())
     {
-      int maxwidth = av.getAlignment().getWidth();
-      int alheight = av.getAlignment().getHeight();
+      drawIdsWrapped(starty, hasHiddenRows);
+      return;
+    }
 
-      if (av.hasHiddenColumns())
-      {
-        maxwidth = av.getAlignment().getHiddenColumns()
-                .findColumnPosition(maxwidth) - 1;
-      }
+    // No need to hang on to labels if we're not wrapped
+    labels = null;
 
-      int annotationHeight = 0;
+    // Now draw the id strings
+    int panelWidth = getWidth();
+    int xPos = 0;
+
+    SequenceI sequence;
+    // Now draw the id strings
+    for (int i = starty; i <= endy; i++)
+    {
+      sequence = av.getAlignment().getSequenceAt(i);
 
-      if (av.isShowAnnotation())
+      if (sequence == null)
       {
-        if (ap == null)
-        {
-          ap = new AnnotationPanel(av);
-        }
+        continue;
+      }
 
-        annotationHeight = ap.adjustPanelHeight();
-        if (labels == null)
-        {
-          labels = new AnnotationLabels(av);
-        }
+      if (hasHiddenRows || av.isDisplayReferenceSeq())
+      {
+        setHiddenFont(sequence);
       }
 
-      int hgap = av.getCharHeight();
-      if (av.getScaleAboveWrapped())
+      // Selected sequence colours
+      if ((searchResults != null) && searchResults.contains(sequence))
       {
-        hgap += av.getCharHeight();
+        currentColor = Color.black;
+        currentTextColor = Color.white;
       }
+      else if ((av.getSelectionGroup() != null)
+              && av.getSelectionGroup().getSequences(null)
+                      .contains(sequence))
+      {
+        currentColor = Color.lightGray;
+        currentTextColor = Color.black;
+      }
+      else
+      {
+        currentColor = av.getSequenceColour(sequence);
+        currentTextColor = Color.black;
+      }
+
+      gg.setColor(currentColor);
+
+      gg.fillRect(0, (i - starty) * av.getCharHeight(), getWidth(),
+              av.getCharHeight());
 
-      int cHeight = alheight * av.getCharHeight() + hgap + annotationHeight;
+      gg.setColor(currentTextColor);
 
-      int rowSize = av.getRanges().getEndRes()
-              - av.getRanges().getStartRes();
+      String string = sequence.getDisplayId(av.getShowJVSuffix());
 
-      // Draw the rest of the panels
-      for (int ypos = hgap, row = av.getRanges().getStartRes(); (ypos <= getHeight())
-              && (row < maxwidth); ypos += cHeight, row += rowSize)
+      if (av.isRightAlignIds())
       {
-        for (int i = starty; i < alheight; i++)
-        {
-          SequenceI s = av.getAlignment().getSequenceAt(i);
-          if (doHiddenCheck)
-          {
-            setHiddenFont(s);
-          }
-          else
-          {
-            gg.setFont(getIdfont());
-          }
-
-          drawIdString(gg, hiddenRows, s, i, 0, ypos);
-        }
+        xPos = panelWidth - fm.stringWidth(string) - 4;
+      }
 
-        if (labels != null && av.isShowAnnotation())
-        {
-          gg.translate(0, ypos + (alheight * av.getCharHeight()));
-          labels.drawComponent(gg, getWidth());
-          gg.translate(0, -ypos - (alheight * av.getCharHeight()));
-        }
+      gg.drawString(string, xPos,
+              (((i - starty) * av.getCharHeight()) + av.getCharHeight())
+                      - (av.getCharHeight() / 5));
+
+      if (hasHiddenRows)
+      {
+        drawMarker(i, starty, 0);
       }
     }
-    else
-    {
-      // No need to hang on to labels if we're not wrapped
-      labels = null;
+  }
 
-      // Now draw the id strings
-      int panelWidth = getWidth();
-      int xPos = 0;
+  /**
+   * Draws sequence ids in wrapped mode
+   * 
+   * @param starty
+   * @param hasHiddenRows
+   */
+  protected void drawIdsWrapped(int starty, boolean hasHiddenRows)
+  {
+    int maxwidth = av.getAlignment().getWidth();
+    int alheight = av.getAlignment().getHeight();
 
-      SequenceI sequence;
-      // Now draw the id strings
-      for (int i = starty; i <= endy; i++)
-      {
-        sequence = av.getAlignment().getSequenceAt(i);
+    if (av.hasHiddenColumns())
+    {
+      maxwidth = av.getAlignment().getHiddenColumns()
+              .findColumnPosition(maxwidth) - 1;
+    }
 
-        if (sequence == null)
-        {
-          continue;
-        }
+    int annotationHeight = 0;
 
-        if (doHiddenCheck)
-        {
-          setHiddenFont(sequence);
-        }
+    if (av.isShowAnnotation())
+    {
+      if (ap == null)
+      {
+        ap = new AnnotationPanel(av);
+      }
 
-        // Selected sequence colours
-        if ((searchResults != null) && searchResults.contains(sequence))
-        {
-          currentColor = Color.black;
-          currentTextColor = Color.white;
-        }
-        else if ((av.getSelectionGroup() != null)
-                && av.getSelectionGroup().getSequences(null)
-                        .contains(sequence))
-        {
-          currentColor = Color.lightGray;
-          currentTextColor = Color.black;
-        }
-        else
-        {
-          currentColor = av.getSequenceColour(sequence);
-          currentTextColor = Color.black;
-        }
+      annotationHeight = ap.adjustPanelHeight();
+      if (labels == null)
+      {
+        labels = new AnnotationLabels(av);
+      }
+    }
 
-        gg.setColor(currentColor);
+    int hgap = av.getCharHeight();
+    if (av.getScaleAboveWrapped())
+    {
+      hgap += av.getCharHeight();
+    }
 
-        gg.fillRect(0, (i - starty) * av.getCharHeight(), getWidth(),
-                av.getCharHeight());
+    int cHeight = alheight * av.getCharHeight() + hgap + annotationHeight;
 
-        gg.setColor(currentTextColor);
+    ViewportRanges ranges = av.getRanges();
 
-        String string = sequence.getDisplayId(av.getShowJVSuffix());
+    int rowSize = ranges.getViewportWidth();
 
-        if (av.isRightAlignIds())
+    /*
+     * draw repeating sequence ids until out of sequence data or
+     * out of visible space, whichever comes first
+     */
+    int ypos = hgap;
+    int row = ranges.getStartRes();
+    while ((ypos <= getHeight()) && (row < maxwidth))
+    {
+      for (int i = starty; i < alheight; i++)
+      {
+        SequenceI s = av.getAlignment().getSequenceAt(i);
+        if (hasHiddenRows || av.isDisplayReferenceSeq())
         {
-          xPos = panelWidth - fm.stringWidth(string) - 4;
+          setHiddenFont(s);
         }
-
-        gg.drawString(string, xPos,
-                (((i - starty) * av.getCharHeight()) + av.getCharHeight())
-                        - (av.getCharHeight() / 5));
-
-        if (hiddenRows)
+        else
         {
-          drawMarker(i, starty, 0);
+          gg.setFont(getIdfont());
         }
 
+        drawIdString(gg, hasHiddenRows, s, i, 0, ypos);
+      }
+
+      if (labels != null && av.isShowAnnotation())
+      {
+        gg.translate(0, ypos + (alheight * av.getCharHeight()));
+        labels.drawComponent(gg, getWidth());
+        gg.translate(0, -ypos - (alheight * av.getCharHeight()));
       }
 
+      ypos += cHeight;
+      row += rowSize;
     }
   }
 
@@ -520,12 +538,25 @@ public class IdCanvas extends JPanel implements ViewportListenerI
     this.idfont = idfont;
   }
 
+  /**
+   * Respond to viewport range changes (e.g. alignment panel was scrolled). Both
+   * scrolling and resizing change viewport ranges. Scrolling changes both start
+   * and end points, but resize only changes end values. Here we only want to
+   * fastpaint on a scroll, with resize using a normal paint, so scroll events
+   * are identified as changes to the horizontal or vertical start value.
+   * <p>
+   * In unwrapped mode, only responds to a vertical scroll, as horizontal scroll
+   * leaves sequence ids unchanged. In wrapped mode, only vertical scroll is
+   * provided, but it generates a change of "startres" which does require an
+   * update here.
+   */
   @Override
   public void propertyChange(PropertyChangeEvent evt)
   {
-    // Respond to viewport range changes (e.g. alignment panel was scrolled)
-    if (evt.getPropertyName().equals("startseq")
-            || evt.getPropertyName().equals("endseq"))
+    String propertyName = evt.getPropertyName();
+    if (propertyName.equals(ViewportRanges.STARTSEQ)
+            || (av.getWrapAlignment() && propertyName
+                    .equals(ViewportRanges.STARTRES)))
     {
       fastPaint((int) evt.getNewValue() - (int) evt.getOldValue());
     }
index 29bb522..099d76a 100755 (executable)
@@ -138,7 +138,7 @@ public class IdPanel extends JPanel implements MouseListener,
     }
 
     lastid = seq;
-    alignPanel.paintAlignment(true);
+    alignPanel.paintAlignment(false);
   }
 
   /**
@@ -313,7 +313,7 @@ public class IdPanel extends JPanel implements MouseListener,
 
     av.isSelectionGroupChanged(true);
 
-    alignPanel.paintAlignment(true);
+    alignPanel.paintAlignment(false);
   }
 
   /**
index 358cca6..8168ce1 100644 (file)
@@ -181,13 +181,13 @@ public class Jalview2XML
    * Map of reconstructed AlignFrame objects that appear to have come from
    * SplitFrame objects (have a dna/protein complement view).
    */
-  private Map<Viewport, AlignFrame> splitFrameCandidates = new HashMap<Viewport, AlignFrame>();
+  private Map<Viewport, AlignFrame> splitFrameCandidates = new HashMap<>();
 
   /*
    * Map from displayed rna structure models to their saved session state jar
    * entry names
    */
-  private Map<RnaModel, String> rnaSessions = new HashMap<RnaModel, String>();
+  private Map<RnaModel, String> rnaSessions = new HashMap<>();
 
   /**
    * create/return unique hash string for sq
@@ -248,19 +248,19 @@ public class Jalview2XML
   {
     if (seqsToIds == null)
     {
-      seqsToIds = new IdentityHashMap<SequenceI, String>();
+      seqsToIds = new IdentityHashMap<>();
     }
     if (seqRefIds == null)
     {
-      seqRefIds = new HashMap<String, SequenceI>();
+      seqRefIds = new HashMap<>();
     }
     if (incompleteSeqs == null)
     {
-      incompleteSeqs = new HashMap<String, SequenceI>();
+      incompleteSeqs = new HashMap<>();
     }
     if (frefedSequence == null)
     {
-      frefedSequence = new ArrayList<SeqFref>();
+      frefedSequence = new ArrayList<>();
     }
   }
 
@@ -459,9 +459,9 @@ public class Jalview2XML
    * This maintains a map of viewports, the key being the seqSetId. Important to
    * set historyItem and redoList for multiple views
    */
-  Map<String, AlignViewport> viewportsAdded = new HashMap<String, AlignViewport>();
+  Map<String, AlignViewport> viewportsAdded = new HashMap<>();
 
-  Map<String, AlignmentAnnotation> annotationIds = new HashMap<String, AlignmentAnnotation>();
+  Map<String, AlignmentAnnotation> annotationIds = new HashMap<>();
 
   String uniqueSetSuffix = "";
 
@@ -537,7 +537,7 @@ public class Jalview2XML
    */
   private void saveAllFrames(List<AlignFrame> frames, JarOutputStream jout)
   {
-    Hashtable<String, AlignFrame> dsses = new Hashtable<String, AlignFrame>();
+    Hashtable<String, AlignFrame> dsses = new Hashtable<>();
 
     /*
      * ensure cached data is clear before starting
@@ -552,8 +552,8 @@ public class Jalview2XML
       // NOTE UTF-8 MUST BE USED FOR WRITING UNICODE CHARS
       // //////////////////////////////////////////////////
 
-      List<String> shortNames = new ArrayList<String>();
-      List<String> viewIds = new ArrayList<String>();
+      List<String> shortNames = new ArrayList<>();
+      List<String> viewIds = new ArrayList<>();
 
       // REVERSE ORDER
       for (int i = frames.size() - 1; i > -1; i--)
@@ -663,7 +663,7 @@ public class Jalview2XML
     {
       FileOutputStream fos = new FileOutputStream(jarFile);
       JarOutputStream jout = new JarOutputStream(fos);
-      List<AlignFrame> frames = new ArrayList<AlignFrame>();
+      List<AlignFrame> frames = new ArrayList<>();
 
       // resolve splitframes
       if (af.getViewport().getCodingComplement() != null)
@@ -749,12 +749,12 @@ public class Jalview2XML
   {
     if (viewIds == null)
     {
-      viewIds = new ArrayList<String>();
+      viewIds = new ArrayList<>();
     }
 
     initSeqRefs();
 
-    List<UserColourScheme> userColours = new ArrayList<UserColourScheme>();
+    List<UserColourScheme> userColours = new ArrayList<>();
 
     AlignViewport av = ap.av;
     ViewportRanges vpRanges = av.getRanges();
@@ -808,9 +808,9 @@ public class Jalview2XML
     }
 
     JSeq jseq;
-    Set<String> calcIdSet = new HashSet<String>();
+    Set<String> calcIdSet = new HashSet<>();
     // record the set of vamsas sequence XML POJO we create.
-    HashMap<String, Sequence> vamsasSetIds = new HashMap<String, Sequence>();
+    HashMap<String, Sequence> vamsasSetIds = new HashMap<>();
     // SAVE SEQUENCES
     for (final SequenceI jds : rjal.getSequences())
     {
@@ -982,7 +982,7 @@ public class Jalview2XML
             pdb.setFile(matchedFile); // entry.getFile());
             if (pdbfiles == null)
             {
-              pdbfiles = new ArrayList<String>();
+              pdbfiles = new ArrayList<>();
             }
 
             if (!pdbfiles.contains(pdbId))
@@ -1131,7 +1131,7 @@ public class Jalview2XML
     /**
      * store forward refs from an annotationRow to any groups
      */
-    IdentityHashMap<SequenceGroup, String> groupRefs = new IdentityHashMap<SequenceGroup, String>();
+    IdentityHashMap<SequenceGroup, String> groupRefs = new IdentityHashMap<>();
     if (storeDS)
     {
       for (SequenceI sq : jal.getSequences())
@@ -1345,7 +1345,7 @@ public class Jalview2XML
                 .getFeatureRenderer().getRenderOrder()
                 .toArray(new String[0]);
 
-        Vector<String> settingsAdded = new Vector<String>();
+        Vector<String> settingsAdded = new Vector<>();
         if (renderOrder != null)
         {
           for (String featureType : renderOrder)
@@ -1388,7 +1388,7 @@ public class Jalview2XML
         // is groups actually supposed to be a map here ?
         Iterator<String> en = ap.getSeqPanel().seqCanvas
                 .getFeatureRenderer().getFeatureGroups().iterator();
-        Vector<String> groupsAdded = new Vector<String>();
+        Vector<String> groupsAdded = new Vector<>();
         while (en.hasNext())
         {
           String grp = en.next();
@@ -1411,17 +1411,16 @@ public class Jalview2XML
       {
         jalview.datamodel.HiddenColumns hidden = av.getAlignment()
                 .getHiddenColumns();
-        if (hidden == null || hidden.getHiddenRegions() == null)
+        if (hidden == null)
         {
           warn("REPORT BUG: avoided null columnselection bug (DMAM reported). Please contact Jim about this.");
         }
         else
         {
-          for (int c = 0; c < hidden.getHiddenRegions()
-                  .size(); c++)
+          ArrayList<int[]> hiddenRegions = hidden
+                  .getHiddenColumnsCopy();
+          for (int[] region : hiddenRegions)
           {
-            int[] region = hidden.getHiddenRegions()
-                    .get(c);
             HiddenColumns hc = new HiddenColumns();
             hc.setStart(region[0]);
             hc.setEnd(region[1]);
@@ -2286,7 +2285,7 @@ public class Jalview2XML
     try
     {
       // create list to store references for any new Jmol viewers created
-      newStructureViewers = new Vector<JalviewStructureDisplayI>();
+      newStructureViewers = new Vector<>();
       // UNMARSHALLER SEEMS TO CLOSE JARINPUTSTREAM, MOST ANNOYING
       // Workaround is to make sure caller implements the JarInputStreamProvider
       // interface
@@ -2379,8 +2378,8 @@ public class Jalview2XML
       initSeqRefs();
     }
     AlignFrame af = null, _af = null;
-    IdentityHashMap<AlignmentI, AlignmentI> importedDatasets = new IdentityHashMap<AlignmentI, AlignmentI>();
-    Map<String, AlignFrame> gatherToThisFrame = new HashMap<String, AlignFrame>();
+    IdentityHashMap<AlignmentI, AlignmentI> importedDatasets = new IdentityHashMap<>();
+    Map<String, AlignFrame> gatherToThisFrame = new HashMap<>();
     final String file = jprovider.getFilename();
     try
     {
@@ -2521,9 +2520,9 @@ public class Jalview2XML
    */
   protected void restoreSplitFrames()
   {
-    List<SplitFrame> gatherTo = new ArrayList<SplitFrame>();
-    List<AlignFrame> addedToSplitFrames = new ArrayList<AlignFrame>();
-    Map<String, AlignFrame> dna = new HashMap<String, AlignFrame>();
+    List<SplitFrame> gatherTo = new ArrayList<>();
+    List<AlignFrame> addedToSplitFrames = new ArrayList<>();
+    Map<String, AlignFrame> dna = new HashMap<>();
 
     /*
      * Identify the DNA alignments
@@ -2665,7 +2664,7 @@ public class Jalview2XML
     errorMessage = null;
   }
 
-  Map<String, String> alreadyLoadedPDB = new HashMap<String, String>();
+  Map<String, String> alreadyLoadedPDB = new HashMap<>();
 
   /**
    * when set, local views will be updated from view stored in JalviewXML
@@ -2836,7 +2835,7 @@ public class Jalview2XML
 
     List<SequenceI> hiddenSeqs = null;
 
-    List<SequenceI> tmpseqs = new ArrayList<SequenceI>();
+    List<SequenceI> tmpseqs = new ArrayList<>();
 
     boolean multipleView = false;
     SequenceI referenceseqForView = null;
@@ -2904,7 +2903,7 @@ public class Jalview2XML
       {
         if (hiddenSeqs == null)
         {
-          hiddenSeqs = new ArrayList<SequenceI>();
+          hiddenSeqs = new ArrayList<>();
         }
 
         hiddenSeqs.add(tmpSeq);
@@ -3111,12 +3110,12 @@ public class Jalview2XML
 
     // ////////////////////////////////
     // LOAD ANNOTATIONS
-    List<JvAnnotRow> autoAlan = new ArrayList<JvAnnotRow>();
+    List<JvAnnotRow> autoAlan = new ArrayList<>();
 
     /*
      * store any annotations which forward reference a group's ID
      */
-    Map<String, List<AlignmentAnnotation>> groupAnnotRefs = new Hashtable<String, List<AlignmentAnnotation>>();
+    Map<String, List<AlignmentAnnotation>> groupAnnotRefs = new Hashtable<>();
 
     if (vamsasSet.getAnnotationCount() > 0)
     {
@@ -3271,7 +3270,7 @@ public class Jalview2XML
                   .get(an[i].getGroupRef());
           if (aal == null)
           {
-            aal = new ArrayList<jalview.datamodel.AlignmentAnnotation>();
+            aal = new ArrayList<>();
             groupAnnotRefs.put(an[i].getGroupRef(), aal);
           }
           aal.add(jaa);
@@ -3361,7 +3360,7 @@ public class Jalview2XML
         }
         int pidThreshold = jGroup.getPidThreshold();
 
-        Vector<SequenceI> seqs = new Vector<SequenceI>();
+        Vector<SequenceI> seqs = new Vector<>();
 
         for (int s = 0; s < jGroup.getSeqCount(); s++)
         {
@@ -3754,7 +3753,7 @@ public class Jalview2XML
      * Run through all PDB ids on the alignment, and collect mappings between
      * distinct view ids and all sequences referring to that view.
      */
-    Map<String, StructureViewerModel> structureViewers = new LinkedHashMap<String, StructureViewerModel>();
+    Map<String, StructureViewerModel> structureViewers = new LinkedHashMap<>();
 
     for (int i = 0; i < jseqs.length; i++)
     {
@@ -3952,8 +3951,8 @@ public class Jalview2XML
 
     Set<Entry<File, StructureData>> fileData = data.getFileData()
             .entrySet();
-    List<PDBEntry> pdbs = new ArrayList<PDBEntry>();
-    List<SequenceI[]> allseqs = new ArrayList<SequenceI[]>();
+    List<PDBEntry> pdbs = new ArrayList<>();
+    List<SequenceI[]> allseqs = new ArrayList<>();
     for (Entry<File, StructureData> pdb : fileData)
     {
       String filePath = pdb.getValue().getFilePath();
@@ -4009,9 +4008,9 @@ public class Jalview2XML
               getViewerJarEntryName(svattrib.getViewId()));
     }
 
-    List<String> pdbfilenames = new ArrayList<String>();
-    List<SequenceI[]> seqmaps = new ArrayList<SequenceI[]>();
-    List<String> pdbids = new ArrayList<String>();
+    List<String> pdbfilenames = new ArrayList<>();
+    List<SequenceI[]> seqmaps = new ArrayList<>();
+    List<String> pdbids = new ArrayList<>();
     StringBuilder newFileLoc = new StringBuilder(64);
     int cp = 0, ncp, ecp;
     Map<File, StructureData> oldFiles = svattrib.getFileData();
@@ -4572,8 +4571,8 @@ public class Jalview2XML
       af.viewport.setFeaturesDisplayed(fdi = new FeaturesDisplayed());
       String[] renderOrder = new String[jms.getFeatureSettings()
               .getSettingCount()];
-      Map<String, FeatureColourI> featureColours = new Hashtable<String, FeatureColourI>();
-      Map<String, Float> featureOrder = new Hashtable<String, Float>();
+      Map<String, FeatureColourI> featureColours = new Hashtable<>();
+      Map<String, Float> featureOrder = new Hashtable<>();
 
       for (int fs = 0; fs < jms.getFeatureSettings().getSettingCount(); fs++)
       {
@@ -4632,7 +4631,7 @@ public class Jalview2XML
           fdi.setVisible(setting.getType());
         }
       }
-      Map<String, Boolean> fgtable = new Hashtable<String, Boolean>();
+      Map<String, Boolean> fgtable = new Hashtable<>();
       for (int gs = 0; gs < jms.getFeatureSettings().getGroupCount(); gs++)
       {
         Group grp = jms.getFeatureSettings().getGroup(gs);
@@ -4825,7 +4824,7 @@ public class Jalview2XML
       String[] magicNames = new String[] { "Consensus", "Quality",
           "Conservation" };
       JvAnnotRow nullAnnot = new JvAnnotRow(-1, null);
-      Hashtable<String, JvAnnotRow> visan = new Hashtable<String, JvAnnotRow>();
+      Hashtable<String, JvAnnotRow> visan = new Hashtable<>();
       for (String nm : magicNames)
       {
         visan.put(nm, nullAnnot);
@@ -4837,11 +4836,11 @@ public class Jalview2XML
                         + auan.template.getCalcId()), auan);
       }
       int hSize = al.getAlignmentAnnotation().length;
-      List<JvAnnotRow> reorder = new ArrayList<JvAnnotRow>();
+      List<JvAnnotRow> reorder = new ArrayList<>();
       // work through any autoCalculated annotation already on the view
       // removing it if it should be placed in a different location on the
       // annotation panel.
-      List<String> remains = new ArrayList<String>(visan.keySet());
+      List<String> remains = new ArrayList<>(visan.keySet());
       for (int h = 0; h < hSize; h++)
       {
         jalview.datamodel.AlignmentAnnotation jalan = al
@@ -5169,7 +5168,7 @@ public class Jalview2XML
   {
     if (datasetIds == null)
     {
-      datasetIds = new Hashtable<String, AlignmentI>();
+      datasetIds = new Hashtable<>();
       return null;
     }
     if (datasetIds.containsKey(datasetId))
@@ -5183,7 +5182,7 @@ public class Jalview2XML
   {
     if (datasetIds == null)
     {
-      datasetIds = new Hashtable<String, AlignmentI>();
+      datasetIds = new Hashtable<>();
     }
     datasetIds.put(datasetId, dataset);
   }
@@ -5206,7 +5205,7 @@ public class Jalview2XML
       // make a new datasetId and record it
       if (dataset2Ids == null)
       {
-        dataset2Ids = new IdentityHashMap<AlignmentI, String>();
+        dataset2Ids = new IdentityHashMap<>();
       }
       else
       {
@@ -5485,11 +5484,11 @@ public class Jalview2XML
         // register sequence object so the XML parser can recover it.
         if (seqRefIds == null)
         {
-          seqRefIds = new HashMap<String, SequenceI>();
+          seqRefIds = new HashMap<>();
         }
         if (seqsToIds == null)
         {
-          seqsToIds = new IdentityHashMap<SequenceI, String>();
+          seqsToIds = new IdentityHashMap<>();
         }
         seqRefIds.put(jv2vobj.get(jvobj).toString(), (SequenceI) jvobj);
         seqsToIds.put((SequenceI) jvobj, id);
index 6f9fbbf..27f9c3f 100644 (file)
@@ -53,6 +53,8 @@ public class OverviewCanvas extends JComponent
 
   private OverviewDimensions od;
 
+  private OverviewRenderer or = null;
+
   private AlignViewportI av;
 
   public OverviewCanvas(OverviewDimensions overviewDims,
@@ -89,6 +91,10 @@ public class OverviewCanvas extends JComponent
       if (updaterunning)
       {
         restart = true;
+        if (or != null)
+        {
+          or.setRedraw(true);
+        }
       }
       else
       {
@@ -120,7 +126,7 @@ public class OverviewCanvas extends JComponent
 
     setPreferredSize(new Dimension(od.getWidth(), od.getHeight()));
 
-    OverviewRenderer or = new OverviewRenderer(sr, fr, od);
+    or = new OverviewRenderer(sr, fr, od);
     miniMe = or.draw(od.getRows(av.getAlignment()),
             od.getColumns(av.getAlignment()));
 
index 3fa674e..7a4456e 100755 (executable)
@@ -107,13 +107,11 @@ public class OverviewPanel extends JPanel implements Runnable,
       @Override
       public void mouseDragged(MouseEvent evt)
       {
-        if (!SwingUtilities.isRightMouseButton(evt)
-                && !av.getWrapAlignment())
+        if (!SwingUtilities.isRightMouseButton(evt))
         {
           od.updateViewportFromMouse(evt.getX(), evt.getY(), av
                   .getAlignment().getHiddenSequences(), av.getAlignment()
                   .getHiddenColumns());
-
         }
       }
     });
@@ -130,7 +128,8 @@ public class OverviewPanel extends JPanel implements Runnable,
             showPopupMenu(evt);
           }
         }
-        else if (!av.getWrapAlignment())
+        else
+        // if (!av.getWrapAlignment())
         {
           od.updateViewportFromMouse(evt.getX(), evt.getY(), av
                   .getAlignment().getHiddenSequences(), av.getAlignment()
@@ -148,7 +147,6 @@ public class OverviewPanel extends JPanel implements Runnable,
       }
     });
 
-
     updateOverviewImage();
   }
 
@@ -206,6 +204,14 @@ public class OverviewPanel extends JPanel implements Runnable,
    */
   public void updateOverviewImage()
   {
+    if (oviewCanvas == null)
+    {
+      /*
+       * panel has been disposed
+       */
+      return;
+    }
+
     if ((getWidth() > 0) && (getHeight() > 0))
     {
       od.setWidth(getWidth());
@@ -222,7 +228,6 @@ public class OverviewPanel extends JPanel implements Runnable,
     Thread thread = new Thread(this);
     thread.start();
     repaint();
-
   }
 
   @Override
@@ -252,4 +257,21 @@ public class OverviewPanel extends JPanel implements Runnable,
   {
     setBoxPosition();
   }
+
+  /**
+   * Removes this object as a property change listener, and nulls references
+   */
+  protected void dispose()
+  {
+    try
+    {
+      av.getRanges().removePropertyChangeListener(this);
+    } finally
+    {
+      av = null;
+      oviewCanvas = null;
+      ap = null;
+      od = null;
+    }
+  }
 }
index a8e2f7e..c78021c 100644 (file)
@@ -1944,11 +1944,18 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
       }
     }
 
-    if (ap.getSeqPanel().seqCanvas.getFeatureRenderer().amendFeatures(seqs,
-            features, true, ap))
+    /*
+     * an entirely gapped region will generate empty lists of sequence / features
+     */
+    if (!seqs.isEmpty())
     {
-      ap.alignFrame.setShowSeqFeatures(true);
-      ap.highlightSearchResults(null);
+      if (ap.getSeqPanel().seqCanvas.getFeatureRenderer().amendFeatures(
+              seqs, features, true, ap))
+      {
+        ap.alignFrame.setShowSeqFeatures(true);
+        ap.av.setSearchResults(null); // clear highlighting
+        ap.repaint(); // draw new/amended features
+      }
     }
   }
 
index cb19539..2302ebe 100755 (executable)
@@ -29,6 +29,7 @@ import jalview.renderer.ScaleRenderer.ScaleMark;
 import jalview.util.MessageManager;
 import jalview.util.Platform;
 import jalview.viewmodel.ViewportListenerI;
+import jalview.viewmodel.ViewportRanges;
 
 import java.awt.Color;
 import java.awt.FontMetrics;
@@ -168,10 +169,6 @@ public class ScalePanel extends JPanel implements MouseMotionListener,
           av.showColumn(reveal[0]);
           reveal = null;
           ap.paintAlignment(true);
-          if (ap.overviewPanel != null)
-          {
-            ap.overviewPanel.updateOverviewImage();
-          }
           av.sendSelection();
         }
       });
@@ -188,10 +185,6 @@ public class ScalePanel extends JPanel implements MouseMotionListener,
             av.showAllHiddenColumns();
             reveal = null;
             ap.paintAlignment(true);
-            if (ap.overviewPanel != null)
-            {
-              ap.overviewPanel.updateOverviewImage();
-            }
             av.sendSelection();
           }
         });
@@ -217,10 +210,6 @@ public class ScalePanel extends JPanel implements MouseMotionListener,
           }
 
           ap.paintAlignment(true);
-          if (ap.overviewPanel != null)
-          {
-            ap.overviewPanel.updateOverviewImage();
-          }
           av.sendSelection();
         }
       });
@@ -401,24 +390,15 @@ public class ScalePanel extends JPanel implements MouseMotionListener,
     int res = (evt.getX() / av.getCharWidth())
             + av.getRanges().getStartRes();
 
+    reveal = av.getAlignment().getHiddenColumns()
+            .getRegionWithEdgeAtRes(res);
+
     res = av.getAlignment().getHiddenColumns().adjustForHiddenColumns(res);
 
-    if (av.getAlignment().getHiddenColumns().getHiddenRegions() != null)
-    {
-      for (int[] region : av.getAlignment().getHiddenColumns()
-              .getHiddenRegions())
-      {
-        if (res + 1 == region[0] || res - 1 == region[1])
-        {
-          reveal = region;
-          ToolTipManager.sharedInstance().registerComponent(this);
-          this.setToolTipText(MessageManager
-                  .getString("label.reveal_hidden_columns"));
-          repaint();
-          return;
-        }
-      }
-    }
+    ToolTipManager.sharedInstance().registerComponent(this);
+    this.setToolTipText(
+            MessageManager.getString("label.reveal_hidden_columns"));
+    repaint();
   }
 
   /**
@@ -430,8 +410,15 @@ public class ScalePanel extends JPanel implements MouseMotionListener,
   @Override
   public void paintComponent(Graphics g)
   {
-    drawScale(g, av.getRanges().getStartRes(), av.getRanges().getEndRes(),
-            getWidth(), getHeight());
+    /*
+     * shouldn't get called in wrapped mode as the scale above is
+     * drawn instead by SeqCanvas.drawNorthScale
+     */
+    if (!av.getWrapAlignment())
+    {
+      drawScale(g, av.getRanges().getStartRes(),
+              av.getRanges().getEndRes(), getWidth(), getHeight());
+    }
   }
 
   // scalewidth will normally be screenwidth,
@@ -499,13 +486,12 @@ public class ScalePanel extends JPanel implements MouseMotionListener,
       gg.setColor(Color.blue);
       int res;
 
-      if (av.getShowHiddenMarkers() && hidden.getHiddenRegions() != null)
+      if (av.getShowHiddenMarkers())
       {
-        for (int i = 0; i < hidden.getHiddenRegions()
-                .size(); i++)
+        List<Integer> positions = hidden.findHiddenRegionPositions();
+        for (int pos : positions)
         {
-          res = hidden.findHiddenRegionPosition(i)
-                  - startx;
+          res = pos - startx;
 
           if (res < 0 || res > widthx)
           {
@@ -556,7 +542,16 @@ public class ScalePanel extends JPanel implements MouseMotionListener,
   public void propertyChange(PropertyChangeEvent evt)
   {
     // Respond to viewport change events (e.g. alignment panel was scrolled)
-    repaint();
+    // Both scrolling and resizing change viewport ranges: scrolling changes
+    // both start and end points, but resize only changes end values.
+    // Here we only want to fastpaint on a scroll, with resize using a normal
+    // paint, so scroll events are identified as changes to the horizontal or
+    // vertical start value.
+    if (evt.getPropertyName().equals(ViewportRanges.STARTRES))
+    {
+      // scroll event, repaint panel
+      repaint();
+    }
   }
 
 }
index 8707e9a..0e31246 100755 (executable)
@@ -52,6 +52,8 @@ import javax.swing.JComponent;
  */
 public class SeqCanvas extends JComponent implements ViewportListenerI
 {
+  private static String ZEROS = "0000000000";
+
   final FeatureRenderer fr;
 
   final SequenceRenderer sr;
@@ -68,9 +70,9 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
 
   boolean fastPaint = false;
 
-  int LABEL_WEST;
+  int labelWidthWest;
 
-  int LABEL_EAST;
+  int labelWidthEast;
 
   int cursorX = 0;
 
@@ -206,7 +208,7 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
 
       if (value != -1)
       {
-        int x = LABEL_WEST - fm.stringWidth(String.valueOf(value))
+        int x = labelWidthWest - fm.stringWidth(String.valueOf(value))
                 - charWidth / 2;
         g.drawString(value + "", x, (ypos + (i * charHeight))
                 - (charHeight / 5));
@@ -286,8 +288,6 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
     fastpainting = true;
     fastPaint = true;
     updateViewport();
-    gg.copyArea(horizontal * charWidth, vertical * charHeight, imgWidth,
-            imgHeight, -horizontal * charWidth, -vertical * charHeight);
 
     ViewportRanges ranges = av.getRanges();
     int startRes = ranges.getStartRes();
@@ -297,6 +297,9 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
     int transX = 0;
     int transY = 0;
 
+    gg.copyArea(horizontal * charWidth, vertical * charHeight, imgWidth,
+            imgHeight, -horizontal * charWidth, -vertical * charHeight);
+
     if (horizontal > 0) // scrollbar pulled right, image to the left
     {
       transX = (endRes - startRes - horizontal) * charWidth;
@@ -411,57 +414,63 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
   }
 
   /**
-   * DOCUMENT ME!
+   * Returns the visible width of the canvas in residues, after allowing for
+   * East or West scales (if shown)
    * 
-   * @param cwidth
-   *          DOCUMENT ME!
+   * @param canvasWidth
+   *          the width in pixels (possibly including scales)
    * 
-   * @return DOCUMENT ME!
+   * @return
    */
-  public int getWrappedCanvasWidth(int cwidth)
+  public int getWrappedCanvasWidth(int canvasWidth)
   {
     FontMetrics fm = getFontMetrics(av.getFont());
 
-    LABEL_EAST = 0;
-    LABEL_WEST = 0;
+    labelWidthEast = 0;
+    labelWidthWest = 0;
 
     if (av.getScaleRightWrapped())
     {
-      LABEL_EAST = fm.stringWidth(getMask());
+      labelWidthEast = getLabelWidth(fm);
     }
 
     if (av.getScaleLeftWrapped())
     {
-      LABEL_WEST = fm.stringWidth(getMask());
+      labelWidthWest = labelWidthEast > 0 ? labelWidthEast
+              : getLabelWidth(fm);
     }
 
-    return (cwidth - LABEL_EAST - LABEL_WEST) / charWidth;
+    return (canvasWidth - labelWidthEast - labelWidthWest) / charWidth;
   }
 
   /**
-   * Generates a string of zeroes.
+   * Returns a pixel width suitable for showing the largest sequence coordinate
+   * (end position) in the alignment. Returns 2 plus the number of decimal
+   * digits to be shown (3 for 1-10, 4 for 11-99 etc).
    * 
-   * @return String
+   * @param fm
+   * @return
    */
-  String getMask()
+  protected int getLabelWidth(FontMetrics fm)
   {
-    String mask = "00";
+    /*
+     * find the biggest sequence end position we need to show
+     * (note this is not necessarily the sequence length)
+     */
     int maxWidth = 0;
-    int tmp;
-    for (int i = 0; i < av.getAlignment().getHeight(); i++)
+    AlignmentI alignment = av.getAlignment();
+    for (int i = 0; i < alignment.getHeight(); i++)
     {
-      tmp = av.getAlignment().getSequenceAt(i).getEnd();
-      if (tmp > maxWidth)
-      {
-        maxWidth = tmp;
-      }
+      maxWidth = Math.max(maxWidth, alignment.getSequenceAt(i).getEnd());
     }
 
+    int length = 2;
     for (int i = maxWidth; i > 0; i /= 10)
     {
-      mask += "0";
+      length++;
     }
-    return mask;
+
+    return fm.stringWidth(ZEROS.substring(0, length));
   }
 
   /**
@@ -482,17 +491,15 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
     updateViewport();
     AlignmentI al = av.getAlignment();
 
-    FontMetrics fm = getFontMetrics(av.getFont());
-
-    if (av.getScaleRightWrapped())
+    int labelWidth = 0;
+    if (av.getScaleRightWrapped() || av.getScaleLeftWrapped())
     {
-      LABEL_EAST = fm.stringWidth(getMask());
+      FontMetrics fm = getFontMetrics(av.getFont());
+      labelWidth = getLabelWidth(fm);
     }
 
-    if (av.getScaleLeftWrapped())
-    {
-      LABEL_WEST = fm.stringWidth(getMask());
-    }
+    labelWidthEast = av.getScaleRightWrapped() ? labelWidth : 0;
+    labelWidthWest = av.getScaleLeftWrapped() ? labelWidth : 0;
 
     int hgap = charHeight;
     if (av.getScaleAboveWrapped())
@@ -500,23 +507,25 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
       hgap += charHeight;
     }
 
-    int cWidth = (canvasWidth - LABEL_EAST - LABEL_WEST) / charWidth;
+    int cWidth = (canvasWidth - labelWidthEast - labelWidthWest) / charWidth;
     int cHeight = av.getAlignment().getHeight() * charHeight;
 
     av.setWrappedWidth(cWidth);
 
-    av.getRanges().setEndRes(av.getRanges().getStartRes() + cWidth - 1);
+    av.getRanges().setViewportStartAndWidth(startRes, cWidth);
 
     int endx;
     int ypos = hgap;
-    int maxwidth = av.getAlignment().getWidth() - 1;
+    int maxwidth = av.getAlignment().getWidth();
 
     if (av.hasHiddenColumns())
     {
       maxwidth = av.getAlignment().getHiddenColumns()
-              .findColumnPosition(maxwidth) - 1;
+              .findColumnPosition(maxwidth);
     }
 
+    int annotationHeight = getAnnotationHeight();
+
     while ((ypos <= canvasHeight) && (startRes < maxwidth))
     {
       endx = startRes + cWidth - 1;
@@ -536,12 +545,12 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
 
       if (av.getScaleRightWrapped())
       {
-        g.translate(canvasWidth - LABEL_EAST, 0);
+        g.translate(canvasWidth - labelWidthEast, 0);
         drawEastScale(g, startRes, endx, ypos);
-        g.translate(-(canvasWidth - LABEL_EAST), 0);
+        g.translate(-(canvasWidth - labelWidthEast), 0);
       }
 
-      g.translate(LABEL_WEST, 0);
+      g.translate(labelWidthWest, 0);
 
       if (av.getScaleAboveWrapped())
       {
@@ -553,9 +562,10 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
         g.setColor(Color.blue);
         int res;
         HiddenColumns hidden = av.getAlignment().getHiddenColumns();
-        for (int i = 0; i < hidden.getHiddenRegions().size(); i++)
+        List<Integer> positions = hidden.findHiddenRegionPositions();
+        for (int pos : positions)
         {
-          res = hidden.findHiddenRegionPosition(i) - startRes;
+          res = pos - startRes;
 
           if (res < 0 || res > endx - startRes)
           {
@@ -601,9 +611,9 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
         g.translate(0, -cHeight - ypos - 3);
       }
       g.setClip(clip);
-      g.translate(-LABEL_WEST, 0);
+      g.translate(-labelWidthWest, 0);
 
-      ypos += cHeight + getAnnotationHeight() + hgap;
+      ypos += cHeight + annotationHeight + hgap;
 
       startRes += cWidth;
     }
@@ -654,15 +664,13 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
     }
     else
     {
-      List<int[]> regions = av.getAlignment().getHiddenColumns()
-              .getHiddenRegions();
-
       int screenY = 0;
       final int screenYMax = endRes - startRes;
       int blockStart = startRes;
       int blockEnd = endRes;
 
-      for (int[] region : regions)
+      for (int[] region : av.getAlignment().getHiddenColumns()
+              .getHiddenColumnsCopy())
       {
         int hideStart = region[0];
         int hideEnd = region[1];
@@ -989,30 +997,36 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
 
   /**
    * Highlights search results in the visible region by rendering as white text
-   * on a black background. Any previous highlighting is removed.
+   * on a black background. Any previous highlighting is removed. Answers true
+   * if any highlight was left on the visible alignment (so status bar should be
+   * set to match), else false.
+   * <p>
+   * Currently fastPaint is not implemented for wrapped alignments. If a wrapped
+   * alignment had to be scrolled to show the highlighted region, then it should
+   * be fully redrawn, otherwise a fast paint can be performed. This argument
+   * could be removed if fast paint of scrolled wrapped alignment is coded in
+   * future (JAL-2609).
    * 
    * @param results
+   * @param noFastPaint
+   * @return
    */
-  public void highlightSearchResults(SearchResultsI results)
+  public boolean highlightSearchResults(SearchResultsI results,
+          boolean noFastPaint)
   {
-    updateViewport();
-
-    /*
-     * for now, don't attempt fastpaint if wrapped format
-     */
-    if (av.getWrapAlignment())
+    if (fastpainting)
     {
-      img = null;
-      av.setSearchResults(results);
-      repaint();
-      return;
+      return false;
     }
-    
-    fastpainting = true;
-    fastPaint = true;
+    boolean wrapped = av.getWrapAlignment();
 
     try
     {
+      fastPaint = !noFastPaint;
+      fastpainting = fastPaint;
+
+      updateViewport();
+
       /*
        * to avoid redrawing the whole visible region, we instead
        * redraw just the minimal regions to remove previous highlights
@@ -1020,12 +1034,34 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
        */
       SearchResultsI previous = av.getSearchResults();
       av.setSearchResults(results);
-      boolean redrawn = drawMappedPositions(previous);
-      redrawn |= drawMappedPositions(results);
+      boolean redrawn = false;
+      boolean drawn = false;
+      if (wrapped)
+      {
+        redrawn = drawMappedPositionsWrapped(previous);
+        drawn = drawMappedPositionsWrapped(results);
+        redrawn |= drawn;
+      }
+      else
+      {
+        redrawn = drawMappedPositions(previous);
+        drawn = drawMappedPositions(results);
+        redrawn |= drawn;
+      }
+
+      /*
+       * if highlights were either removed or added, repaint
+       */
       if (redrawn)
       {
         repaint();
       }
+
+      /*
+       * return true only if highlights were added
+       */
+      return drawn;
+
     } finally
     {
       fastpainting = false;
@@ -1128,30 +1164,189 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
   @Override
   public void propertyChange(PropertyChangeEvent evt)
   {
-    if (!av.getWrapAlignment())
+    String eventName = evt.getPropertyName();
+
+    if (av.getWrapAlignment())
     {
-      if (evt.getPropertyName().equals("startres")
-              || evt.getPropertyName().equals("endres"))
+      if (eventName.equals(ViewportRanges.STARTRES))
+      {
+        repaint();
+      }
+    }
+    else
+    {
+      int scrollX = 0;
+      if (eventName.equals(ViewportRanges.STARTRES))
       {
         // Make sure we're not trying to draw a panel
         // larger than the visible window
         ViewportRanges vpRanges = av.getRanges();
-        int scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
-        if (scrollX > vpRanges.getEndRes() - vpRanges.getStartRes())
+        scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
+        int range = vpRanges.getEndRes() - vpRanges.getStartRes();
+        if (scrollX > range)
         {
-          scrollX = vpRanges.getEndRes() - vpRanges.getStartRes();
+          scrollX = range;
         }
-        else if (scrollX < vpRanges.getStartRes() - vpRanges.getEndRes())
+        else if (scrollX < -range)
         {
-          scrollX = vpRanges.getStartRes() - vpRanges.getEndRes();
+          scrollX = -range;
         }
+      }
+
+      // Both scrolling and resizing change viewport ranges: scrolling changes
+      // both start and end points, but resize only changes end values.
+      // Here we only want to fastpaint on a scroll, with resize using a normal
+      // paint, so scroll events are identified as changes to the horizontal or
+      // vertical start value.
+      if (eventName.equals(ViewportRanges.STARTRES))
+      {
+        // scroll - startres and endres both change
         fastPaint(scrollX, 0);
       }
-      else if (evt.getPropertyName().equals("startseq")
-              || evt.getPropertyName().equals("endseq"))
+      else if (eventName.equals(ViewportRanges.STARTSEQ))
       {
+        // scroll
         fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
       }
     }
   }
+
+  /**
+   * Redraws any positions in the search results in the visible region of a
+   * wrapped alignment. Any highlights are drawn depending on the search results
+   * set on the Viewport, not the <code>results</code> argument. This allows
+   * this method to be called either to clear highlights (passing the previous
+   * search results), or to draw new highlights.
+   * 
+   * @param results
+   * @return
+   */
+  protected boolean drawMappedPositionsWrapped(SearchResultsI results)
+  {
+    if (results == null)
+    {
+      return false;
+    }
+  
+    boolean matchFound = false;
+
+    int wrappedWidth = av.getWrappedWidth();
+    int wrappedHeight = getRepeatHeightWrapped();
+
+    ViewportRanges ranges = av.getRanges();
+    int canvasHeight = getHeight();
+    int repeats = canvasHeight / wrappedHeight;
+    if (canvasHeight / wrappedHeight > 0)
+    {
+      repeats++;
+    }
+
+    int firstVisibleColumn = ranges.getStartRes();
+    int lastVisibleColumn = ranges.getStartRes() + repeats
+            * ranges.getViewportWidth() - 1;
+
+    AlignmentI alignment = av.getAlignment();
+    if (av.hasHiddenColumns())
+    {
+      firstVisibleColumn = alignment.getHiddenColumns()
+              .adjustForHiddenColumns(firstVisibleColumn);
+      lastVisibleColumn = alignment.getHiddenColumns()
+              .adjustForHiddenColumns(lastVisibleColumn);
+    }
+
+    int gapHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
+
+    for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
+            .getEndSeq(); seqNo++)
+    {
+      SequenceI seq = alignment.getSequenceAt(seqNo);
+
+      int[] visibleResults = results.getResults(seq, firstVisibleColumn,
+              lastVisibleColumn);
+      if (visibleResults != null)
+      {
+        for (int i = 0; i < visibleResults.length - 1; i += 2)
+        {
+          int firstMatchedColumn = visibleResults[i];
+          int lastMatchedColumn = visibleResults[i + 1];
+          if (firstMatchedColumn <= lastVisibleColumn
+                  && lastMatchedColumn >= firstVisibleColumn)
+          {
+            /*
+             * found a search results match in the visible region
+             */
+            firstMatchedColumn = Math.max(firstMatchedColumn,
+                    firstVisibleColumn);
+            lastMatchedColumn = Math.min(lastMatchedColumn,
+                    lastVisibleColumn);
+
+            /*
+             * draw each mapped position separately (as contiguous positions may
+             * wrap across lines)
+             */
+            for (int mappedPos = firstMatchedColumn; mappedPos <= lastMatchedColumn; mappedPos++)
+            {
+              int displayColumn = mappedPos;
+              if (av.hasHiddenColumns())
+              {
+                displayColumn = alignment.getHiddenColumns()
+                        .findColumnPosition(displayColumn);
+              }
+
+              /*
+               * transX: offset from left edge of canvas to residue position
+               */
+              int transX = labelWidthWest
+                      + ((displayColumn - ranges.getStartRes()) % wrappedWidth)
+                      * av.getCharWidth();
+
+              /*
+               * transY: offset from top edge of canvas to residue position
+               */
+              int transY = gapHeight;
+              transY += (displayColumn - ranges.getStartRes())
+                      / wrappedWidth * wrappedHeight;
+              transY += (seqNo - ranges.getStartSeq()) * av.getCharHeight();
+
+              /*
+               * yOffset is from graphics origin to start of visible region
+               */
+              int yOffset = 0;// (displayColumn / wrappedWidth) * wrappedHeight;
+              if (transY < getHeight())
+              {
+                matchFound = true;
+                gg.translate(transX, transY);
+                drawPanel(gg, displayColumn, displayColumn, seqNo, seqNo,
+                        yOffset);
+                gg.translate(-transX, -transY);
+              }
+            }
+          }
+        }
+      }
+    }
+  
+    return matchFound;
+  }
+
+  /**
+   * Answers the height in pixels of a repeating section of the wrapped
+   * alignment, including space above, scale above if shown, sequences, and
+   * annotation panel if shown
+   * 
+   * @return
+   */
+  protected int getRepeatHeightWrapped()
+  {
+    // gap (and maybe scale) above
+    int repeatHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
+
+    // add sequences
+    repeatHeight += av.getRanges().getViewportHeight() * charHeight;
+
+    // add annotations panel height if shown
+    repeatHeight += getAnnotationHeight();
+
+    return repeatHeight;
+  }
 }
index 5634ead..2b1b0e5 100644 (file)
@@ -201,6 +201,7 @@ public class SeqPanel extends JPanel implements MouseListener,
     int res = 0;
     int x = evt.getX();
 
+    int startRes = av.getRanges().getStartRes();
     if (av.getWrapAlignment())
     {
 
@@ -215,7 +216,7 @@ public class SeqPanel extends JPanel implements MouseListener,
 
       int y = evt.getY();
       y -= hgap;
-      x -= seqCanvas.LABEL_WEST;
+      x = Math.max(0, x - seqCanvas.labelWidthWest);
 
       int cwidth = seqCanvas.getWrappedCanvasWidth(this.getWidth());
       if (cwidth < 1)
@@ -224,10 +225,11 @@ public class SeqPanel extends JPanel implements MouseListener,
       }
 
       wrappedBlock = y / cHeight;
-      wrappedBlock += av.getRanges().getStartRes() / cwidth;
-
-      res = wrappedBlock * cwidth + x / av.getCharWidth();
-
+      wrappedBlock += startRes / cwidth;
+      // allow for wrapped view scrolled right (possible from Overview)
+      int startOffset = startRes % cwidth;
+      res = wrappedBlock * cwidth
+              + Math.min(cwidth - 1, startOffset + x / av.getCharWidth());
     }
     else
     {
@@ -237,7 +239,7 @@ public class SeqPanel extends JPanel implements MouseListener,
         // right-hand gutter
         x = seqCanvas.getX() + seqCanvas.getWidth();
       }
-      res = (x / av.getCharWidth()) + av.getRanges().getStartRes();
+      res = (x / av.getCharWidth()) + startRes;
       if (res > av.getRanges().getEndRes())
       {
         // moused off right
@@ -681,6 +683,8 @@ public class SeqPanel extends JPanel implements MouseListener,
     }
     lastSearchResults = results;
 
+    boolean wasScrolled = false;
+
     if (av.isFollowHighlight())
     {
       // don't allow highlight of protein/cDNA to also scroll a complementary
@@ -688,14 +692,19 @@ public class SeqPanel extends JPanel implements MouseListener,
       // over residue to change abruptly, causing highlighted residue in panel 2
       // to change, causing a scroll in panel 1 etc)
       ap.setToScrollComplementPanel(false);
-      if (ap.scrollToPosition(results, false))
+      wasScrolled = ap.scrollToPosition(results, false);
+      if (wasScrolled)
       {
         seqCanvas.revalidate();
       }
       ap.setToScrollComplementPanel(true);
     }
-    setStatusMessage(results);
-    seqCanvas.highlightSearchResults(results);
+
+    boolean noFastPaint = wasScrolled && av.getWrapAlignment();
+    if (seqCanvas.highlightSearchResults(results, noFastPaint))
+    {
+      setStatusMessage(results);
+    }
   }
 
   @Override
@@ -1592,7 +1601,7 @@ public class SeqPanel extends JPanel implements MouseListener,
         SearchResultsI highlight = new SearchResults();
         highlight.addResult(sequence, features.get(0).getBegin(), features
                 .get(0).getEnd());
-        seqCanvas.highlightSearchResults(highlight);
+        seqCanvas.highlightSearchResults(highlight, false);
 
         /*
          * open the Amend Features dialog; clear highlighting afterwards,
@@ -1601,7 +1610,8 @@ public class SeqPanel extends JPanel implements MouseListener,
         List<SequenceI> seqs = Collections.singletonList(sequence);
         seqCanvas.getFeatureRenderer().amendFeatures(seqs, features, false,
                 ap);
-        seqCanvas.highlightSearchResults(null);
+        av.setSearchResults(null); // clear highlighting
+        seqCanvas.repaint(); // draw new/amended features
       }
     }
   }
@@ -2147,8 +2157,7 @@ public class SeqPanel extends JPanel implements MouseListener,
 
     if (copycolsel
             && av.hasHiddenColumns()
-            && (av.getAlignment().getHiddenColumns() == null || av
-                    .getAlignment().getHiddenColumns().getHiddenRegions() == null))
+            && (av.getAlignment().getHiddenColumns() == null))
     {
       System.err.println("Bad things");
     }
index 0c4e03e..ec53e93 100755 (executable)
@@ -20,6 +20,7 @@
  */
 package jalview.gui;
 
+import jalview.analysis.Conservation;
 import jalview.datamodel.SequenceGroup;
 import jalview.jbgui.GSliderPanel;
 import jalview.renderer.ResidueShaderI;
@@ -28,6 +29,7 @@ import jalview.util.MessageManager;
 import java.awt.event.MouseAdapter;
 import java.awt.event.MouseEvent;
 import java.beans.PropertyVetoException;
+import java.util.List;
 
 import javax.swing.JInternalFrame;
 import javax.swing.JLayeredPane;
@@ -170,7 +172,8 @@ public class SliderPanel extends GSliderPanel
             "label.conservation_colour_increment",
             new String[] { source == null ? BACKGROUND : source }));
 
-    if (ap.av.getAlignment().getGroups() != null)
+    List<SequenceGroup> groups = ap.av.getAlignment().getGroups();
+    if (groups != null && !groups.isEmpty())
     {
       sliderPanel.setAllGroupsCheckEnabled(true);
     }
@@ -331,13 +334,14 @@ public class SliderPanel extends GSliderPanel
     {
       ap.av.setThreshold(percent);
     }
-    updateColourScheme(percent, cs);
+    updateColourScheme(percent, cs, null);
 
     if (allGroupsCheck.isSelected())
     {
-      for (SequenceGroup sg : ap.av.getAlignment().getGroups())
+      List<SequenceGroup> groups = ap.av.getAlignment().getGroups();
+      for (SequenceGroup sg : groups)
       {
-        updateColourScheme(percent, sg.getGroupColourScheme());
+        updateColourScheme(percent, sg.getGroupColourScheme(), sg);
       }
     }
 
@@ -350,8 +354,10 @@ public class SliderPanel extends GSliderPanel
    * 
    * @param percent
    * @param scheme
+   * @param sg
    */
-  protected void updateColourScheme(int percent, ResidueShaderI scheme)
+  protected void updateColourScheme(int percent, ResidueShaderI scheme,
+          SequenceGroup sg)
   {
     if (scheme == null)
     {
@@ -359,6 +365,20 @@ public class SliderPanel extends GSliderPanel
     }
     if (forConservation)
     {
+      if (!scheme.conservationApplied())
+      {
+        /*
+         * first time the colour scheme has had Conservation shading applied
+         * - compute conservation
+         */
+        Conservation c = new Conservation("Group", sg.getSequences(null),
+                sg.getStartRes(), sg.getEndRes());
+        c.calculate();
+        c.verdict(false, ap.av.getConsPercGaps());
+        sg.cs.setConservation(c);
+
+      }
+      scheme.setConservationApplied(true);
       scheme.setConservationInc(percent);
     }
     else
@@ -376,6 +396,7 @@ public class SliderPanel extends GSliderPanel
   public void setAllGroupsCheckEnabled(boolean b)
   {
     allGroupsCheck.setEnabled(b);
+    allGroupsCheck.setSelected(ap.av.getColourAppliesToAllGroups());
   }
 
   /**
index c3e71da..b669ee5 100755 (executable)
@@ -171,22 +171,9 @@ public class AnnotationFile
     if (cs != null && cs.hasHiddenColumns())
     {
       text.append("VIEW_HIDECOLS\t");
-      List<int[]> hc = cs.getHiddenRegions();
-      boolean comma = false;
-      for (int[] r : hc)
-      {
-        if (!comma)
-        {
-          comma = true;
-        }
-        else
-        {
-          text.append(",");
-        }
-        text.append(r[0]);
-        text.append("-");
-        text.append(r[1]);
-      }
+
+      String regions = cs.regionsToString(",", "-");
+      text.append(regions);
       text.append("\n");
     }
     // TODO: allow efficient recovery of annotation data shown in several
@@ -202,8 +189,8 @@ public class AnnotationFile
       StringBuffer colours = new StringBuffer();
       StringBuffer graphLine = new StringBuffer();
       StringBuffer rowprops = new StringBuffer();
-      Hashtable<Integer, String> graphGroup = new Hashtable<Integer, String>();
-      Hashtable<Integer, Object[]> graphGroup_refs = new Hashtable<Integer, Object[]>();
+      Hashtable<Integer, String> graphGroup = new Hashtable<>();
+      Hashtable<Integer, Object[]> graphGroup_refs = new Hashtable<>();
       BitSet graphGroupSeen = new BitSet();
 
       java.awt.Color color;
@@ -748,8 +735,8 @@ public class AnnotationFile
           BufferedReader in) throws Exception
   {
     nlinesread = 0;
-    ArrayList<Object[]> combineAnnotation_calls = new ArrayList<Object[]>();
-    ArrayList<Object[]> deferredAnnotation_calls = new ArrayList<Object[]>();
+    ArrayList<Object[]> combineAnnotation_calls = new ArrayList<>();
+    ArrayList<Object[]> deferredAnnotation_calls = new ArrayList<>();
     boolean modified = false;
     String groupRef = null;
     Hashtable groupRefRows = new Hashtable();
@@ -1112,7 +1099,7 @@ public class AnnotationFile
         modified = true;
       }
       // Resolve the groupRefs
-      Hashtable<String, SequenceGroup> groupRefLookup = new Hashtable<String, SequenceGroup>();
+      Hashtable<String, SequenceGroup> groupRefLookup = new Hashtable<>();
       Enumeration en = groupRefRows.keys();
 
       while (en.hasMoreElements())
index 14574d0..36fe35a 100644 (file)
@@ -279,18 +279,8 @@ public class JSONFile extends AlignFile implements ComplexAlignFile
     // hidden column business
     if (getViewport().hasHiddenColumns())
     {
-      List<int[]> hiddenCols = getViewport().getAlignment()
-              .getHiddenColumns()
-              .getHiddenRegions();
-      StringBuilder hiddenColsBuilder = new StringBuilder();
-      for (int[] range : hiddenCols)
-      {
-        hiddenColsBuilder.append(";").append(range[0]).append("-")
-                .append(range[1]);
-      }
-
-      hiddenColsBuilder.deleteCharAt(0);
-      hiddenSections[0] = hiddenColsBuilder.toString();
+      hiddenSections[0] = getViewport().getAlignment().getHiddenColumns()
+              .regionsToString(";", "-");
     }
 
     // hidden rows/seqs business
@@ -323,7 +313,7 @@ public class JSONFile extends AlignFile implements ComplexAlignFile
           SequenceI[] sqs)
   {
     displayedFeatures = (fr == null) ? null : fr.getFeaturesDisplayed();
-    List<SequenceFeaturesPojo> sequenceFeaturesPojo = new ArrayList<SequenceFeaturesPojo>();
+    List<SequenceFeaturesPojo> sequenceFeaturesPojo = new ArrayList<>();
     if (sqs == null)
     {
       return sequenceFeaturesPojo;
@@ -371,7 +361,7 @@ public class JSONFile extends AlignFile implements ComplexAlignFile
   public static List<AlignmentAnnotationPojo> annotationToJsonPojo(
           Vector<AlignmentAnnotation> annotations)
   {
-    List<AlignmentAnnotationPojo> jsonAnnotations = new ArrayList<AlignmentAnnotationPojo>();
+    List<AlignmentAnnotationPojo> jsonAnnotations = new ArrayList<>();
     if (annotations == null)
     {
       return jsonAnnotations;
@@ -468,8 +458,8 @@ public class JSONFile extends AlignFile implements ComplexAlignFile
         parseHiddenCols(jvSettingsJsonObj);
       }
 
-      hiddenSequences = new ArrayList<SequenceI>();
-      seqMap = new Hashtable<String, Sequence>();
+      hiddenSequences = new ArrayList<>();
+      seqMap = new Hashtable<>();
       for (Iterator<JSONObject> sequenceIter = seqJsonArray.iterator(); sequenceIter
               .hasNext();)
       {
@@ -512,7 +502,7 @@ public class JSONFile extends AlignFile implements ComplexAlignFile
         int endRes = Integer.valueOf(seqGrpObj.get("endRes").toString());
         JSONArray sequenceRefs = (JSONArray) seqGrpObj.get("sequenceRefs");
 
-        ArrayList<SequenceI> grpSeqs = new ArrayList<SequenceI>();
+        ArrayList<SequenceI> grpSeqs = new ArrayList<>();
         if (sequenceRefs.size() > 0)
         {
           Iterator<String> seqHashIter = sequenceRefs.iterator();
@@ -646,7 +636,7 @@ public class JSONFile extends AlignFile implements ComplexAlignFile
 
   public void parseHiddenSeqRefsAsList(JSONObject jvSettingsJson)
   {
-    hiddenSeqRefs = new ArrayList<String>();
+    hiddenSeqRefs = new ArrayList<>();
     String hiddenSeqs = (String) jvSettingsJson.get("hiddenSeqs");
     if (hiddenSeqs != null && !hiddenSeqs.isEmpty())
     {
index 9291ca6..46490cd 100644 (file)
@@ -48,12 +48,14 @@ public class OverviewRenderer
   // raw number of pixels to allocate to each row
   private float pixelsPerSeq;
 
+  // flag to indicate whether to halt drawing
+  private volatile boolean redraw = false;
+
   public OverviewRenderer(jalview.api.SequenceRenderer seqRenderer,
           FeatureRenderer fr, OverviewDimensions od)
-  // FeatureColourFinder colfinder, OverviewDimensions od)
   {
     sr = seqRenderer;
-    finder = new FeatureColourFinder(fr); // colfinder;
+    finder = new FeatureColourFinder(fr);
 
     pixelsPerCol = od.getPixelsPerCol();
     pixelsPerSeq = od.getPixelsPerSeq();
@@ -76,8 +78,14 @@ public class OverviewRenderer
     int rgbcolor = Color.white.getRGB();
     int seqIndex = 0;
     int pixelRow = 0;
+
     for (int alignmentRow : rows)
     {
+      if (redraw)
+      {
+        break;
+      }
+
       // get details of this alignment row
       boolean hidden = rows.isHidden(alignmentRow);
       SequenceI seq = rows.getSequence(alignmentRow);
@@ -90,6 +98,11 @@ public class OverviewRenderer
       int pixelCol = 0;
       for (int alignmentCol : cols)
       {
+        if (redraw)
+        {
+          break;
+        }
+
         // calculate where this column extends to in pixels
         int endCol = Math.min(
                 Math.round((colIndex + 1) * pixelsPerCol) - 1,
@@ -171,6 +184,10 @@ public class OverviewRenderer
     int pixelCol = 0;
     for (int alignmentCol : cols)
     {
+      if (redraw)
+      {
+        break;
+      }
       if (alignmentCol >= annotations.length)
       {
         break; // no more annotations to draw here
@@ -205,4 +222,12 @@ public class OverviewRenderer
       }
     }
   }
+
+  public void setRedraw(boolean b)
+  {
+    synchronized (this)
+    {
+      redraw = b;
+    }
+  }
 }
index b6f7fe6..8ecb2ad 100644 (file)
@@ -96,6 +96,20 @@ public class ResidueShader implements ResidueShaderI
   }
 
   /**
+   * Copy constructor
+   */
+  public ResidueShader(ResidueShader rs)
+  {
+    this.colourScheme = rs.colourScheme;
+    this.consensus = rs.consensus;
+    this.conservation = rs.conservation;
+    this.conservationColouring = rs.conservationColouring;
+    this.conservationIncrement = rs.conservationIncrement;
+    this.ignoreGaps = rs.ignoreGaps;
+    this.pidThreshold = rs.pidThreshold;
+  }
+
+  /**
    * @see jalview.renderer.ResidueShaderI#setConsensus(jalview.datamodel.ProfilesI)
    */
   @Override
index 541288e..8feee1f 100644 (file)
@@ -309,14 +309,39 @@ public class FeatureRenderer extends FeatureRendererModel
       for (SequenceFeature sf : overlaps)
       {
         Color featureColour = fc.getColor(sf);
-        int visibleStart = Math.max(sf.getBegin(),
-                visiblePositions.getBegin());
+        if (featureColour == null)
+        {
+          // score feature outwith threshold for colouring
+          continue;
+        }
+
+        /*
+         * if feature starts/ends outside the visible range,
+         * restrict to visible positions (or if a contact feature,
+         * to a single position)
+         */
+        int visibleStart = sf.getBegin();
+        if (visibleStart < visiblePositions.getBegin())
+        {
+          visibleStart = sf.isContactFeature() ? sf.getEnd()
+                  : visiblePositions.getBegin();
+        }
+        int visibleEnd = sf.getEnd();
+        if (visibleEnd > visiblePositions.getEnd())
+        {
+          visibleEnd = sf.isContactFeature() ? sf.getBegin()
+                  : visiblePositions.getEnd();
+        }
+
         int featureStartCol = seq.findIndex(visibleStart);
-        int visibleEnd = Math.min(sf.getEnd(), visiblePositions.getEnd());
         int featureEndCol = sf.begin == sf.end ? featureStartCol : seq
                 .findIndex(visibleEnd);
 
-        if (sf.isContactFeature())
+        // Color featureColour = getColour(sequenceFeature);
+
+        boolean isContactFeature = sf.isContactFeature();
+
+        if (isContactFeature)
         {
           boolean drawn = renderFeature(g, seq, featureStartCol - 1,
                   featureStartCol - 1, featureColour, start, end, y1,
@@ -329,7 +354,7 @@ public class FeatureRenderer extends FeatureRendererModel
             drawnColour = featureColour;
           }
         }
-        else if (showFeature(sf))
+        else
         {
           /*
            * showing feature score by height of colour
@@ -391,7 +416,8 @@ public class FeatureRenderer extends FeatureRendererModel
    * Returns the sequence feature colour rendered at the given column position,
    * or null if none found. The feature of highest render order (i.e. on top) is
    * found, subject to both feature type and feature group being visible, and
-   * its colour returned.
+   * its colour returned. This method is suitable when no feature transparency
+   * applied (only the topmost visible feature colour is rendered).
    * <p>
    * Note this method does not check for a gap in the column so would return the
    * colour for features enclosing a gapped column. Check for gap before calling
@@ -427,7 +453,11 @@ public class FeatureRenderer extends FeatureRendererModel
       {
         if (!featureGroupNotShown(sequenceFeature))
         {
-          return getColour(sequenceFeature);
+          Color col = getColour(sequenceFeature);
+          if (col != null)
+          {
+            return col;
+          }
         }
       }
     }
index dbe4901..b748d9e 100644 (file)
@@ -551,16 +551,27 @@ public class FeatureColour implements FeatureColourI
       return getColour();
     }
 
-    // todo should we check for above/below threshold here?
-    if (range == 0.0)
-    {
-      return getMaxColour();
-    }
+    /*
+     * graduated colour case, optionally with threshold
+     * Float.NaN is assigned minimum visible score colour
+     */
     float scr = feature.getScore();
     if (Float.isNaN(scr))
     {
       return getMinColour();
     }
+    if (isAboveThreshold() && scr <= threshold)
+    {
+      return null;
+    }
+    if (isBelowThreshold() && scr >= threshold)
+    {
+      return null;
+    }
+    if (range == 0.0)
+    {
+      return getMaxColour();
+    }
     float scl = (scr - base) / range;
     if (isHighToLow)
     {
@@ -602,44 +613,6 @@ public class FeatureColour implements FeatureColourI
     return (isHighToLow) ? (base + range) : base;
   }
 
-  /**
-   * Answers true if the feature has a simple colour, or is coloured by label,
-   * or has a graduated colour and the score of this feature instance is within
-   * the range to render (if any), i.e. does not lie below or above any
-   * threshold set.
-   * 
-   * @param feature
-   * @return
-   */
-  @Override
-  public boolean isColored(SequenceFeature feature)
-  {
-    if (isColourByLabel() || !isGraduatedColour())
-    {
-      return true;
-    }
-
-    float val = feature.getScore();
-    if (Float.isNaN(val))
-    {
-      return true;
-    }
-    if (Float.isNaN(this.threshold))
-    {
-      return true;
-    }
-
-    if (isAboveThreshold() && val <= threshold)
-    {
-      return false;
-    }
-    if (isBelowThreshold() && val >= threshold)
-    {
-      return false;
-    }
-    return true;
-  }
-
   @Override
   public boolean isSimpleColour()
   {
index 03fc129..b47b82e 100755 (executable)
@@ -24,7 +24,6 @@ import jalview.datamodel.AnnotatedCollectionI;
 import jalview.datamodel.SequenceCollectionI;
 import jalview.datamodel.SequenceGroup;
 import jalview.datamodel.SequenceI;
-import jalview.util.Comparison;
 
 import java.awt.Color;
 import java.util.Map;
@@ -91,7 +90,7 @@ public abstract class ResidueColourScheme implements ColourSchemeI
   {
     Color colour = Color.white;
 
-    if (!Comparison.isGap(c) && colors != null && symbolIndex != null
+    if (colors != null && symbolIndex != null
             && c < symbolIndex.length
             && symbolIndex[c] < colors.length)
     {
index aa20121..e1b60ca 100755 (executable)
@@ -23,6 +23,7 @@ package jalview.schemes;
 import jalview.datamodel.AnnotatedCollectionI;
 import jalview.datamodel.SequenceCollectionI;
 import jalview.datamodel.SequenceI;
+import jalview.util.Comparison;
 
 import java.awt.Color;
 import java.util.Map;
@@ -35,13 +36,10 @@ import java.util.Map;
  */
 public class ScoreColourScheme extends ResidueColourScheme
 {
-  /** DOCUMENT ME!! */
   public double min;
 
-  /** DOCUMENT ME!! */
   public double max;
 
-  /** DOCUMENT ME!! */
   public double[] scores;
 
   /**
@@ -65,25 +63,38 @@ public class ScoreColourScheme extends ResidueColourScheme
 
     // Make colours in constructor
     // Why wasn't this done earlier?
-    int i, iSize = scores.length;
+    int iSize = scores.length;
     colors = new Color[scores.length];
-    for (i = 0; i < iSize; i++)
+    for (int i = 0; i < iSize; i++)
     {
-      float red = (float) (scores[i] - (float) min) / (float) (max - min);
+      /*
+       * scale score between min and max to the range 0.0 - 1.0
+       */
+      float score = (float) (scores[i] - (float) min) / (float) (max - min);
 
-      if (red > 1.0f)
+      if (score > 1.0f)
       {
-        red = 1.0f;
+        score = 1.0f;
       }
 
-      if (red < 0.0f)
+      if (score < 0.0f)
       {
-        red = 0.0f;
+        score = 0.0f;
       }
-      colors[i] = makeColour(red);
+      colors[i] = makeColour(score);
     }
   }
 
+  @Override
+  public Color findColour(char c, int j, SequenceI seq)
+  {
+    if (Comparison.isGap(c))
+    {
+      return Color.white;
+    }
+    return super.findColour(c);
+  }
+
   /**
    * DOCUMENT ME!
    * 
index 926ccc7..b2ec120 100644 (file)
@@ -108,7 +108,7 @@ public final class MappingUtils
      * Cache a copy of the target sequences so we can mimic successive edits on
      * them. This lets us compute mappings for all edits in the set.
      */
-    Map<SequenceI, SequenceI> targetCopies = new HashMap<SequenceI, SequenceI>();
+    Map<SequenceI, SequenceI> targetCopies = new HashMap<>();
     for (SequenceI seq : mapTo.getSequences())
     {
       SequenceI ds = seq.getDatasetSequence();
@@ -433,7 +433,7 @@ public final class MappingUtils
           boolean undo, AlignmentI mapTo, List<AlignedCodonFrame> mappings)
   {
     SequenceI[] sortOrder = command.getSequenceOrder(undo);
-    List<SequenceI> mappedOrder = new ArrayList<SequenceI>();
+    List<SequenceI> mappedOrder = new ArrayList<>();
     int j = 0;
 
     /*
@@ -518,7 +518,6 @@ public final class MappingUtils
     AlignViewportI protein = targetIsNucleotide ? mapFrom : mapTo;
     List<AlignedCodonFrame> codonFrames = protein.getAlignment()
             .getCodonFrames();
-    // ColumnSelection mappedColumns = new ColumnSelection();
 
     if (colsel == null)
     {
@@ -540,7 +539,7 @@ public final class MappingUtils
               toSequences, fromGapChar);
     }
 
-    for (int[] hidden : hiddencols.getHiddenRegions())
+    for (int[] hidden : hiddencols.getHiddenColumnsCopy())
     {
       mapHiddenColumns(hidden, codonFrames, newHidden, fromSequences,
               toSequences, fromGapChar);
@@ -696,7 +695,7 @@ public final class MappingUtils
   public static List<char[]> findCodonsFor(SequenceI seq, int col,
           List<AlignedCodonFrame> mappings)
   {
-    List<char[]> result = new ArrayList<char[]>();
+    List<char[]> result = new ArrayList<>();
     int dsPos = seq.findPosition(col);
     for (AlignedCodonFrame mapping : mappings)
     {
@@ -775,7 +774,7 @@ public final class MappingUtils
           SequenceI sequence, List<AlignedCodonFrame> mappings,
           List<SequenceI> filterList)
   {
-    List<AlignedCodonFrame> result = new ArrayList<AlignedCodonFrame>();
+    List<AlignedCodonFrame> result = new ArrayList<>();
     if (sequence == null || mappings == null)
     {
       return result;
index 60cee46..5a7a27f 100644 (file)
@@ -405,6 +405,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
   public void setWrapAlignment(boolean state)
   {
     viewStyle.setWrapAlignment(state);
+    ranges.setWrappedMode(state);
   }
 
   /**
@@ -1159,7 +1160,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
   @Override
   public boolean hasHiddenColumns()
   {
-    return colSel != null
+    return alignment.getHiddenColumns() != null
             && alignment.getHiddenColumns().hasHiddenColumns();
   }
 
@@ -1390,6 +1391,9 @@ public abstract class AlignmentViewport implements AlignViewportI,
   // common hide/show seq stuff
   public void showAllHiddenSeqs()
   {
+    int startSeq = ranges.getStartSeq();
+    int endSeq = ranges.getEndSeq();
+
     if (alignment.getHiddenSequences().getSize() > 0)
     {
       if (selectionGroup == null)
@@ -1407,6 +1411,8 @@ public abstract class AlignmentViewport implements AlignViewportI,
 
       hiddenRepSequences = null;
 
+      ranges.setStartEndSeq(startSeq, endSeq + tmp.size());
+
       firePropertyChange("alignment", null, alignment.getSequences());
       // used to set hasHiddenRows/hiddenRepSequences here, after the property
       // changed event
@@ -1416,6 +1422,9 @@ public abstract class AlignmentViewport implements AlignViewportI,
 
   public void showSequence(int index)
   {
+    int startSeq = ranges.getStartSeq();
+    int endSeq = ranges.getEndSeq();
+
     List<SequenceI> tmp = alignment.getHiddenSequences().showSequence(
             index, hiddenRepSequences);
     if (tmp.size() > 0)
@@ -1431,6 +1440,9 @@ public abstract class AlignmentViewport implements AlignViewportI,
         selectionGroup.addSequence(seq, false);
         setSequenceAnnotationsVisible(seq, true);
       }
+
+      ranges.setStartEndSeq(startSeq, endSeq + tmp.size());
+
       firePropertyChange("alignment", null, alignment.getSequences());
       sendSelection();
     }
@@ -1452,6 +1464,11 @@ public abstract class AlignmentViewport implements AlignViewportI,
 
   public void hideSequence(SequenceI[] seq)
   {
+    /*
+     * cache offset to first visible sequence
+     */
+    int startSeq = ranges.getStartSeq();
+
     if (seq != null)
     {
       for (int i = 0; i < seq.length; i++)
@@ -1459,6 +1476,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
         alignment.getHiddenSequences().hideSequence(seq[i]);
         setSequenceAnnotationsVisible(seq[i], false);
       }
+      ranges.setStartSeq(startSeq);
       firePropertyChange("alignment", null, alignment.getSequences());
     }
   }
@@ -2870,4 +2888,45 @@ public abstract class AlignmentViewport implements AlignViewportI,
   {
     return searchResults;
   }
+
+  /**
+   * get the consensus sequence as displayed under the PID consensus annotation
+   * row.
+   * 
+   * @return consensus sequence as a new sequence object
+   */
+  public SequenceI getConsensusSeq()
+  {
+    if (consensus == null)
+    {
+      updateConsensus(null);
+    }
+    if (consensus == null)
+    {
+      return null;
+    }
+    StringBuffer seqs = new StringBuffer();
+    for (int i = 0; i < consensus.annotations.length; i++)
+    {
+      Annotation annotation = consensus.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("Consensus", seqs.toString());
+    sq.setDescription("Percentage Identity Consensus "
+            + ((ignoreGapsInConsensusCalculation) ? " without gaps" : ""));
+    return sq;
+  }
 }
index d2912d8..7ac07ac 100644 (file)
@@ -231,7 +231,8 @@ public abstract class OverviewDimensions
     resetAlignmentDims();
 
     // boxX, boxY is the x,y location equivalent to startRes, startSeq
-    boxX = Math.round((float) startRes * width / alwidth);
+    int xPos = Math.min(startRes, alwidth - vpwidth + 1);
+    boxX = Math.round((float) xPos * width / alwidth);
     boxY = Math.round((float) startSeq * sequencesHeight / alheight);
 
     // boxWidth is the width in residues translated to pixels
index 4bf7694..4d64f1c 100644 (file)
@@ -39,6 +39,11 @@ public class OverviewDimensionsHideHidden extends OverviewDimensions
       y = 0;
     }
 
+    if (ranges.isWrappedMode())
+    {
+      y = 0; // sorry, no vertical scroll when wrapped
+    }
+
     //
     // Convert x value to residue position
     //
index 4b396a6..62e8000 100644 (file)
@@ -81,6 +81,11 @@ public class OverviewDimensionsShowHidden extends OverviewDimensions
       y = 0;
     }
 
+    if (ranges.isWrappedMode())
+    {
+      y = 0; // sorry, no vertical scroll when wrapped
+    }
+
     //
     // Convert x value to residue position
     //
@@ -135,10 +140,12 @@ public class OverviewDimensionsShowHidden extends OverviewDimensions
     // so convert back after getting visible region position
     yAsSeq = hiddenSeqs.adjustForHiddenSeqs(hiddenSeqs
             .findIndexWithoutHiddenSeqs(yAsSeq));
+    yAsSeq = Math.max(yAsSeq, 0); // -1 if before first visible sequence
 
     // check in case we went off the edge of the alignment
     int visAlignHeight = hiddenSeqs.findIndexWithoutHiddenSeqs(alheight);
     int visYAsSeq = hiddenSeqs.findIndexWithoutHiddenSeqs(yAsSeq);
+    visYAsSeq = Math.max(visYAsSeq, 0); // -1 if before first visible sequence
     if (visYAsSeq + vpheight - 1 > visAlignHeight)
     {
       // went past the end of the alignment, adjust backwards
@@ -156,7 +163,6 @@ public class OverviewDimensionsShowHidden extends OverviewDimensions
     // update viewport
     ranges.setStartRes(visXAsRes);
     ranges.setStartSeq(visYAsSeq);
-
   }
 
   /**
index 4eb8c95..49d0f65 100644 (file)
@@ -32,6 +32,16 @@ import jalview.datamodel.HiddenColumns;
  */
 public class ViewportRanges extends ViewportProperties
 {
+  public static final String STARTRES = "startres";
+
+  public static final String ENDRES = "endres";
+
+  public static final String STARTSEQ = "startseq";
+
+  public static final String ENDSEQ = "endseq";
+
+  private boolean wrappedMode = false;
+
   // start residue of viewport
   private int startRes;
 
@@ -121,9 +131,14 @@ public class ViewportRanges extends ViewportProperties
   public void setStartEndRes(int start, int end)
   {
     int oldstartres = this.startRes;
-    if (start > getVisibleAlignmentWidth() - 1)
+
+    /*
+     * if not wrapped, don't leave white space at the right margin
+     */
+    int lastColumn = getVisibleAlignmentWidth() - 1;
+    if (!wrappedMode && (start > lastColumn))
     {
-      startRes = Math.max(getVisibleAlignmentWidth() - 1, 0);
+      startRes = Math.max(lastColumn, 0);
     }
     else if (start < 0)
     {
@@ -139,39 +154,22 @@ public class ViewportRanges extends ViewportProperties
     {
       endRes = 0;
     }
-    else if (end > getVisibleAlignmentWidth() - 1)
+    else if (!wrappedMode && (end > lastColumn))
     {
-      endRes = Math.max(getVisibleAlignmentWidth() - 1, 0);
+      endRes = Math.max(lastColumn, 0);
     }
     else
     {
       endRes = end;
     }
 
-    changeSupport.firePropertyChange("startres", oldstartres, startRes);
+    changeSupport.firePropertyChange(STARTRES, oldstartres, startRes);
     if (oldstartres == startRes)
     {
       // event won't be fired if start positions are same
       // fire an event for the end positions in case they changed
-      changeSupport.firePropertyChange("endres", oldendres, endRes);
-    }
-  }
-
-  /**
-   * Set last residue visible in the viewport. Fires a property change event.
-   * 
-   * @param res
-   *          residue position
-   */
-  public void setEndRes(int res)
-  {
-    int startres = res;
-    int width = getViewportWidth();
-    if (startres + width - 1 > getVisibleAlignmentWidth() - 1)
-    {
-      startres = getVisibleAlignmentWidth() - width;
+      changeSupport.firePropertyChange(ENDRES, oldendres, endRes);
     }
-    setStartEndRes(startres - width + 1, startres);
   }
 
   /**
@@ -206,9 +204,10 @@ public class ViewportRanges extends ViewportProperties
   public void setStartEndSeq(int start, int end)
   {
     int oldstartseq = this.startSeq;
-    if (start > getVisibleAlignmentHeight() - 1)
+    int visibleHeight = getVisibleAlignmentHeight();
+    if (start > visibleHeight - 1)
     {
-      startSeq = Math.max(getVisibleAlignmentHeight() - 1, 0);
+      startSeq = Math.max(visibleHeight - 1, 0);
     }
     else if (start < 0)
     {
@@ -220,9 +219,9 @@ public class ViewportRanges extends ViewportProperties
     }
 
     int oldendseq = this.endSeq;
-    if (end >= getVisibleAlignmentHeight())
+    if (end >= visibleHeight)
     {
-      endSeq = Math.max(getVisibleAlignmentHeight() - 1, 0);
+      endSeq = Math.max(visibleHeight - 1, 0);
     }
     else if (end < 0)
     {
@@ -233,12 +232,12 @@ public class ViewportRanges extends ViewportProperties
       endSeq = end;
     }
 
-    changeSupport.firePropertyChange("startseq", oldstartseq, startSeq);
+    changeSupport.firePropertyChange(STARTSEQ, oldstartseq, startSeq);
     if (oldstartseq == startSeq)
     {
       // event won't be fired if start positions are the same
       // fire in case the end positions changed
-      changeSupport.firePropertyChange("endseq", oldendseq, endSeq);
+      changeSupport.firePropertyChange(ENDSEQ, oldendseq, endSeq);
     }
   }
 
@@ -330,12 +329,18 @@ public class ViewportRanges extends ViewportProperties
     {
       vpstart = 0;
     }
-    else if ((w <= getVisibleAlignmentWidth())
-            && (vpstart + w - 1 > getVisibleAlignmentWidth() - 1))
-    // viewport width is less than the full alignment and we are running off the
-    // RHS edge
+
+    /*
+     * if not wrapped, don't leave white space at the right margin
+     */
+    if (!wrappedMode)
     {
-      vpstart = getVisibleAlignmentWidth() - w;
+      if ((w <= getVisibleAlignmentWidth())
+              && (vpstart + w - 1 > getVisibleAlignmentWidth() - 1))
+      {
+        vpstart = getVisibleAlignmentWidth() - w;
+      }
+
     }
     setStartEndRes(vpstart, vpstart + w - 1);
   }
@@ -451,18 +456,42 @@ public class ViewportRanges extends ViewportProperties
   }
 
   /**
-   * Scroll a wrapped alignment so that the specified residue is visible. Fires
-   * a property change event.
+   * Scroll a wrapped alignment so that the specified residue is in the first
+   * repeat of the wrapped view. Fires a property change event. Answers true if
+   * the startRes changed, else false.
    * 
    * @param res
    *          residue position to scroll to
+   * @return
    */
-  public void scrollToWrappedVisible(int res)
+  public boolean scrollToWrappedVisible(int res)
   {
-    // get the start residue of the wrapped row which res is in
-    // and set that as our start residue
+    int oldStartRes = startRes;
     int width = getViewportWidth();
-    setStartRes((res / width) * width);
+
+    if (res >= oldStartRes && res < oldStartRes + width)
+    {
+      return false;
+    }
+
+    boolean up = res < oldStartRes;
+    int widthsToScroll = Math.abs((res - oldStartRes) / width);
+    if (up)
+    {
+      widthsToScroll++;
+    }
+
+    int residuesToScroll = width * widthsToScroll;
+    int newStartRes = up ? oldStartRes - residuesToScroll : oldStartRes
+            + residuesToScroll;
+    if (newStartRes < 0)
+    {
+      newStartRes = 0;
+    }
+
+    setStartRes(newStartRes);
+
+    return true;
   }
 
   /**
@@ -506,7 +535,15 @@ public class ViewportRanges extends ViewportProperties
    */
   public void pageUp()
   {
-    setViewportStartAndHeight(2 * startSeq - endSeq, getViewportHeight());
+    if (wrappedMode)
+    {
+      setStartRes(Math.max(0, getStartRes() - getViewportWidth()));
+    }
+    else
+    {
+      setViewportStartAndHeight(startSeq - (endSeq - startSeq),
+              getViewportHeight());
+    }
   }
   
   /**
@@ -514,6 +551,94 @@ public class ViewportRanges extends ViewportProperties
    */
   public void pageDown()
   {
-    setViewportStartAndHeight(endSeq, getViewportHeight());
+    if (wrappedMode)
+    {
+      /*
+       * if height is more than width (i.e. not all sequences fit on screen),
+       * increase page down to height
+       */
+      int newStart = getStartRes()
+              + Math.max(getViewportHeight(), getViewportWidth());
+
+      /*
+       * don't page down beyond end of alignment, or if not all
+       * sequences fit in the visible height
+       */
+      if (newStart < getVisibleAlignmentWidth())
+      {
+        setStartRes(newStart);
+      }
+    }
+    else
+    {
+      setViewportStartAndHeight(endSeq, getViewportHeight());
+    }
+  }
+
+  public void setWrappedMode(boolean wrapped)
+  {
+    wrappedMode = wrapped;
+  }
+
+  public boolean isWrappedMode()
+  {
+    return wrappedMode;
+  }
+
+  /**
+   * Answers the vertical scroll position (0..) to set, given the visible column
+   * that is at top left.
+   * 
+   * <pre>
+   * Example:
+   *    viewport width 40 columns (0-39, 40-79, 80-119...)
+   *    column 0 returns scroll position 0
+   *    columns 1-40 return scroll position 1
+   *    columns 41-80 return scroll position 2
+   *    etc
+   * </pre>
+   * 
+   * @param topLeftColumn
+   *          (0..)
+   * @return
+   */
+  public int getWrappedScrollPosition(final int topLeftColumn)
+  {
+    int w = getViewportWidth();
+
+    /*
+     * visible whole widths
+     */
+    int scroll = topLeftColumn / w;
+
+    /*
+     * add 1 for a part width if there is one
+     */
+    scroll += topLeftColumn % w > 0 ? 1 : 0;
+
+    return scroll;
+  }
+
+  /**
+   * Answers the maximum wrapped vertical scroll value, given the column
+   * position (0..) to show at top left of the visible region.
+   * 
+   * @param topLeftColumn
+   * @return
+   */
+  public int getWrappedMaxScroll(int topLeftColumn)
+  {
+    int scrollPosition = getWrappedScrollPosition(topLeftColumn);
+
+    /*
+     * how many more widths could be drawn after this one?
+     */
+    int columnsRemaining = getVisibleAlignmentWidth() - topLeftColumn;
+    int width = getViewportWidth();
+    int widthsRemaining = columnsRemaining / width
+            + (columnsRemaining % width > 0 ? 1 : 0) - 1;
+    int maxScroll = scrollPosition + widthsRemaining;
+
+    return maxScroll;
   }
 }
index de1ee5e..1d09dca 100644 (file)
@@ -559,7 +559,8 @@ public abstract class FeatureRendererModel implements
    * Returns the configured colour for a particular feature instance. This
    * includes calculation of 'colour by label', or of a graduated score colour,
    * if applicable. It does not take into account feature visibility or colour
-   * transparency.
+   * transparency. Returns null for a score feature whose score value lies
+   * outside any colour threshold.
    * 
    * @param feature
    * @return
@@ -571,19 +572,6 @@ public abstract class FeatureRendererModel implements
   }
 
   /**
-   * Answers true unless the feature has a graduated colour scheme and the
-   * feature value lies outside the current threshold for display
-   * 
-   * @param sequenceFeature
-   * @return
-   */
-  protected boolean showFeature(SequenceFeature sequenceFeature)
-  {
-    FeatureColourI fc = getFeatureStyle(sequenceFeature.type);
-    return fc.isColored(sequenceFeature);
-  }
-
-  /**
    * Answers true if the feature type is currently selected to be displayed,
    * else false
    * 
index 2adefc9..4b5d096 100644 (file)
@@ -37,7 +37,6 @@ import jalview.io.FormatAdapter;
 import jalview.util.MapList;
 
 import java.io.IOException;
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Iterator;
 import java.util.List;
@@ -1107,35 +1106,6 @@ public class AlignmentTest
             "addSequence broke dataset reference integrity");
   }
 
-  @Test(groups = "Functional")
-  public void getVisibleStartAndEndIndexTest()
-  {
-    Sequence seq = new Sequence("testSeq", "ABCDEFGHIJKLMNOPQRSTUVWXYZ");
-    AlignmentI align = new Alignment(new SequenceI[] { seq });
-    ArrayList<int[]> hiddenCols = new ArrayList<int[]>();
-
-    int[] startEnd = align.getVisibleStartAndEndIndex(hiddenCols);
-    assertEquals(0, startEnd[0]);
-    assertEquals(25, startEnd[1]);
-
-    hiddenCols.add(new int[] { 0, 0 });
-    startEnd = align.getVisibleStartAndEndIndex(hiddenCols);
-    assertEquals(1, startEnd[0]);
-    assertEquals(25, startEnd[1]);
-
-    hiddenCols.add(new int[] { 6, 9 });
-    hiddenCols.add(new int[] { 11, 12 });
-    startEnd = align.getVisibleStartAndEndIndex(hiddenCols);
-    assertEquals(1, startEnd[0]);
-    assertEquals(25, startEnd[1]);
-
-    hiddenCols.add(new int[] { 24, 25 });
-    startEnd = align.getVisibleStartAndEndIndex(hiddenCols);
-    System.out.println(startEnd[0] + " : " + startEnd[1]);
-    assertEquals(1, startEnd[0]);
-    assertEquals(23, startEnd[1]);
-  }
-
   /**
    * Tests that dbrefs with mappings to sequence get updated if the sequence
    * acquires a dataset sequence
index 7237e63..e99e952 100644 (file)
@@ -132,7 +132,7 @@ public class ColumnSelectionTest
     // hide column 5 (and adjacent):
     cs.hideSelectedColumns(5, al.getHiddenColumns());
     // 4,5,6 now hidden:
-    List<int[]> hidden = al.getHiddenColumns().getHiddenRegions();
+    List<int[]> hidden = al.getHiddenColumns().getHiddenColumnsCopy();
     assertEquals(1, hidden.size());
     assertEquals("[4, 6]", Arrays.toString(hidden.get(0)));
     // none now selected:
@@ -145,7 +145,7 @@ public class ColumnSelectionTest
     cs.addElement(5);
     cs.addElement(6);
     cs.hideSelectedColumns(4, al.getHiddenColumns());
-    hidden = al.getHiddenColumns().getHiddenRegions();
+    hidden = al.getHiddenColumns().getHiddenColumnsCopy();
     assertEquals(1, hidden.size());
     assertEquals("[4, 6]", Arrays.toString(hidden.get(0)));
     assertTrue(cs.getSelected().isEmpty());
@@ -157,7 +157,7 @@ public class ColumnSelectionTest
     cs.addElement(5);
     cs.addElement(6);
     cs.hideSelectedColumns(6, al.getHiddenColumns());
-    hidden = al.getHiddenColumns().getHiddenRegions();
+    hidden = al.getHiddenColumns().getHiddenColumnsCopy();
     assertEquals(1, hidden.size());
     assertEquals("[4, 6]", Arrays.toString(hidden.get(0)));
     assertTrue(cs.getSelected().isEmpty());
@@ -168,7 +168,7 @@ public class ColumnSelectionTest
     cs.addElement(4);
     cs.addElement(6);
     cs.hideSelectedColumns(5, al.getHiddenColumns());
-    hidden = al.getHiddenColumns().getHiddenRegions();
+    hidden = al.getHiddenColumns().getHiddenColumnsCopy();
     assertEquals(1, hidden.size());
     assertEquals("[4, 6]", Arrays.toString(hidden.get(0)));
     assertTrue(cs.getSelected().isEmpty());
@@ -196,7 +196,7 @@ public class ColumnSelectionTest
 
     cs.hideSelectedColumns(al);
     assertTrue(cs.getSelected().isEmpty());
-    List<int[]> hidden = cols.getHiddenRegions();
+    List<int[]> hidden = cols.getHiddenColumnsCopy();
     assertEquals(4, hidden.size());
     assertEquals("[2, 4]", Arrays.toString(hidden.get(0)));
     assertEquals("[7, 9]", Arrays.toString(hidden.get(1)));
index 10808d6..7c88d71 100644 (file)
@@ -20,9 +20,9 @@
  */
 package jalview.datamodel;
 
+import static org.testng.Assert.assertNull;
 import static org.testng.AssertJUnit.assertEquals;
 import static org.testng.AssertJUnit.assertFalse;
-import static org.testng.AssertJUnit.assertSame;
 import static org.testng.AssertJUnit.assertTrue;
 
 import jalview.analysis.AlignmentGenerator;
@@ -232,14 +232,17 @@ public class HiddenColumnsTest
     HiddenColumns cs = new HiddenColumns();
     cs.hideColumns(10, 11);
     cs.hideColumns(5, 7);
-    assertEquals("[5, 7]", Arrays.toString(cs.getHiddenRegions().get(0)));
+    assertEquals("[5, 7]",
+            Arrays.toString(cs.getHiddenColumnsCopy().get(0)));
 
     HiddenColumns cs2 = new HiddenColumns(cs);
     assertTrue(cs2.hasHiddenColumns());
-    assertEquals(2, cs2.getHiddenRegions().size());
+    assertEquals(2, cs2.getHiddenColumnsCopy().size());
     // hidden columns are held in column order
-    assertEquals("[5, 7]", Arrays.toString(cs2.getHiddenRegions().get(0)));
-    assertEquals("[10, 11]", Arrays.toString(cs2.getHiddenRegions().get(1)));
+    assertEquals("[5, 7]",
+            Arrays.toString(cs2.getHiddenColumnsCopy().get(0)));
+    assertEquals("[10, 11]",
+            Arrays.toString(cs2.getHiddenColumnsCopy().get(1)));
   }
 
   /**
@@ -336,66 +339,78 @@ public class HiddenColumnsTest
     ColumnSelection colsel = new ColumnSelection();
     HiddenColumns cs = al.getHiddenColumns();
     colsel.hideSelectedColumns(5, al.getHiddenColumns());
-    List<int[]> hidden = cs.getHiddenRegions();
+    List<int[]> hidden = cs.getHiddenColumnsCopy();
     assertEquals(1, hidden.size());
     assertEquals("[5, 5]", Arrays.toString(hidden.get(0)));
 
     colsel.hideSelectedColumns(3, al.getHiddenColumns());
+    hidden = cs.getHiddenColumnsCopy();
     assertEquals(2, hidden.size());
     // two hidden ranges, in order:
-    assertSame(hidden, cs.getHiddenRegions());
+    assertEquals(hidden.size(), cs.getHiddenColumnsCopy().size());
     assertEquals("[3, 3]", Arrays.toString(hidden.get(0)));
     assertEquals("[5, 5]", Arrays.toString(hidden.get(1)));
 
     // hiding column 4 expands [3, 3] to [3, 4]
     // and merges to [5, 5] to make [3, 5]
     colsel.hideSelectedColumns(4, al.getHiddenColumns());
-    hidden = cs.getHiddenRegions();
+    hidden = cs.getHiddenColumnsCopy();
     assertEquals(1, hidden.size());
     assertEquals("[3, 5]", Arrays.toString(hidden.get(0)));
 
     // clear hidden columns (note they are added to selected)
     cs.revealAllHiddenColumns(colsel);
     // it is now actually null but getter returns an empty list
-    assertTrue(cs.getHiddenRegions().isEmpty());
+    assertTrue(cs.getHiddenColumnsCopy().isEmpty());
 
     cs.hideColumns(3, 6);
-    hidden = cs.getHiddenRegions();
+    hidden = cs.getHiddenColumnsCopy();
     int[] firstHiddenRange = hidden.get(0);
     assertEquals("[3, 6]", Arrays.toString(firstHiddenRange));
 
     // adding a subrange of already hidden should do nothing
     cs.hideColumns(4, 5);
+    hidden = cs.getHiddenColumnsCopy();
     assertEquals(1, hidden.size());
-    assertSame(firstHiddenRange, cs.getHiddenRegions().get(0));
+    assertEquals("[3, 6]",
+            Arrays.toString(cs.getHiddenColumnsCopy().get(0)));
     cs.hideColumns(3, 5);
+    hidden = cs.getHiddenColumnsCopy();
     assertEquals(1, hidden.size());
-    assertSame(firstHiddenRange, cs.getHiddenRegions().get(0));
+    assertEquals("[3, 6]",
+            Arrays.toString(cs.getHiddenColumnsCopy().get(0)));
     cs.hideColumns(4, 6);
+    hidden = cs.getHiddenColumnsCopy();
     assertEquals(1, hidden.size());
-    assertSame(firstHiddenRange, cs.getHiddenRegions().get(0));
+    assertEquals("[3, 6]",
+            Arrays.toString(cs.getHiddenColumnsCopy().get(0)));
     cs.hideColumns(3, 6);
+    hidden = cs.getHiddenColumnsCopy();
     assertEquals(1, hidden.size());
-    assertSame(firstHiddenRange, cs.getHiddenRegions().get(0));
+    assertEquals("[3, 6]",
+            Arrays.toString(cs.getHiddenColumnsCopy().get(0)));
 
     cs.revealAllHiddenColumns(colsel);
     cs.hideColumns(2, 4);
-    hidden = cs.getHiddenRegions();
+    hidden = cs.getHiddenColumnsCopy();
     assertEquals(1, hidden.size());
     assertEquals("[2, 4]", Arrays.toString(hidden.get(0)));
 
     // extend contiguous with 2 positions overlap
     cs.hideColumns(3, 5);
+    hidden = cs.getHiddenColumnsCopy();
     assertEquals(1, hidden.size());
     assertEquals("[2, 5]", Arrays.toString(hidden.get(0)));
 
     // extend contiguous with 1 position overlap
     cs.hideColumns(5, 6);
+    hidden = cs.getHiddenColumnsCopy();
     assertEquals(1, hidden.size());
     assertEquals("[2, 6]", Arrays.toString(hidden.get(0)));
 
     // extend contiguous with overlap both ends:
     cs.hideColumns(1, 7);
+    hidden = cs.getHiddenColumnsCopy();
     assertEquals(1, hidden.size());
     assertEquals("[1, 7]", Arrays.toString(hidden.get(0)));
   }
@@ -413,7 +428,7 @@ public class HiddenColumnsTest
     colsel.addElement(10);
     cs.revealHiddenColumns(5, colsel);
     // hidden columns list now null but getter returns empty list:
-    assertTrue(cs.getHiddenRegions().isEmpty());
+    assertTrue(cs.getHiddenColumnsCopy().isEmpty());
     // revealed columns are marked as selected (added to selection):
     assertEquals("[10, 5, 6, 7, 8]", colsel.getSelected().toString());
 
@@ -421,9 +436,9 @@ public class HiddenColumnsTest
     colsel = new ColumnSelection();
     cs = new HiddenColumns();
     cs.hideColumns(5, 8);
-    List<int[]> hidden = cs.getHiddenRegions();
+    List<int[]> hidden = cs.getHiddenColumnsCopy();
     cs.revealHiddenColumns(6, colsel);
-    assertSame(hidden, cs.getHiddenRegions());
+    assertEquals(hidden.size(), cs.getHiddenColumnsCopy().size());
     assertTrue(colsel.getSelected().isEmpty());
   }
 
@@ -442,7 +457,7 @@ public class HiddenColumnsTest
      * revealing hidden columns adds them (in order) to the (unordered)
      * selection list
      */
-    assertTrue(cs.getHiddenRegions().isEmpty());
+    assertTrue(cs.getHiddenColumnsCopy().isEmpty());
     assertEquals("[11, 1, 2, 3, 5, 6, 7, 8]", colsel.getSelected()
             .toString());
   }
@@ -478,13 +493,13 @@ public class HiddenColumnsTest
     HiddenColumns cs = new HiddenColumns();
     cs.hideColumns(49, 59);
     cs.hideColumns(69, 79);
-    List<int[]> hidden = cs.getHiddenRegions();
+    List<int[]> hidden = cs.getHiddenColumnsCopy();
     assertEquals(2, hidden.size());
     assertEquals("[49, 59]", Arrays.toString(hidden.get(0)));
     assertEquals("[69, 79]", Arrays.toString(hidden.get(1)));
 
     cs.hideColumns(48, 80);
-    hidden = cs.getHiddenRegions();
+    hidden = cs.getHiddenColumnsCopy();
     assertEquals(1, hidden.size());
     assertEquals("[48, 80]", Arrays.toString(hidden.get(0)));
 
@@ -497,7 +512,7 @@ public class HiddenColumnsTest
     cs.hideColumns(50, 60);
     // hiding 21-49 should merge to one range
     cs.hideColumns(21, 49);
-    hidden = cs.getHiddenRegions();
+    hidden = cs.getHiddenColumnsCopy();
     assertEquals(1, hidden.size());
     assertEquals("[10, 60]", Arrays.toString(hidden.get(0)));
 
@@ -513,7 +528,7 @@ public class HiddenColumnsTest
     cs.hideColumns(60, 70);
 
     cs.hideColumns(15, 45);
-    hidden = cs.getHiddenRegions();
+    hidden = cs.getHiddenColumnsCopy();
     assertEquals(2, hidden.size());
     assertEquals("[10, 50]", Arrays.toString(hidden.get(0)));
     assertEquals("[60, 70]", Arrays.toString(hidden.get(1)));
@@ -530,23 +545,23 @@ public class HiddenColumnsTest
     one.set(1);
     cs = new HiddenColumns();
     cs.hideMarkedBits(one);
-    assertEquals(1, cs.getHiddenRegions().size());
+    assertEquals(1, cs.getHiddenColumnsCopy().size());
 
     one.set(2);
     cs = new HiddenColumns();
     cs.hideMarkedBits(one);
-    assertEquals(1, cs.getHiddenRegions().size());
+    assertEquals(1, cs.getHiddenColumnsCopy().size());
 
     one.set(3);
     cs = new HiddenColumns();
     cs.hideMarkedBits(one);
-    assertEquals(1, cs.getHiddenRegions().size());
+    assertEquals(1, cs.getHiddenColumnsCopy().size());
 
     // split
     one.clear(2);
     cs = new HiddenColumns();
     cs.hideMarkedBits(one);
-    assertEquals(2, cs.getHiddenRegions().size());
+    assertEquals(2, cs.getHiddenColumnsCopy().size());
 
     assertEquals(0, cs.adjustForHiddenColumns(0));
     assertEquals(2, cs.adjustForHiddenColumns(1));
@@ -557,7 +572,7 @@ public class HiddenColumnsTest
     cs = new HiddenColumns();
     cs.hideMarkedBits(one);
 
-    assertEquals(1, cs.getHiddenRegions().size());
+    assertEquals(1, cs.getHiddenColumnsCopy().size());
 
     assertEquals(0, cs.adjustForHiddenColumns(0));
     assertEquals(1, cs.adjustForHiddenColumns(1));
@@ -585,4 +600,135 @@ public class HiddenColumnsTest
       assertEquals(toMark, fromMark);
     }
   }
+
+  @Test(groups = { "Functional" })
+  public void testFindHiddenRegionPositions()
+  {
+    HiddenColumns hc = new HiddenColumns();
+
+    List<Integer> positions = hc.findHiddenRegionPositions();
+    assertTrue(positions.isEmpty());
+
+    hc.hideColumns(3, 7);
+    hc.hideColumns(10, 10);
+    hc.hideColumns(14, 15);
+
+    positions = hc.findHiddenRegionPositions();
+    assertEquals(3, positions.size());
+    assertEquals(3, positions.get(0).intValue());
+    assertEquals(5, positions.get(1).intValue());
+    assertEquals(8, positions.get(2).intValue());
+  }
+
+  @Test(groups = { "Functional" })
+  public void testRegionsToString()
+  {
+    HiddenColumns hc = new HiddenColumns();
+
+    String result = hc.regionsToString(",", "--");
+    assertEquals("", result);
+
+    hc.hideColumns(3, 7);
+    hc.hideColumns(10, 10);
+    hc.hideColumns(14, 15);
+
+    result = hc.regionsToString(",", "--");
+    assertEquals("3--7,10--10,14--15", result);
+  }
+
+  @Test(groups = "Functional")
+  public void getVisibleStartAndEndIndexTest()
+  {
+    Sequence seq = new Sequence("testSeq", "ABCDEFGHIJKLMNOPQRSTUVWXYZ");
+    AlignmentI align = new Alignment(new SequenceI[] { seq });
+    HiddenColumns hc = new HiddenColumns();
+
+    int[] startEnd = hc.getVisibleStartAndEndIndex(align.getWidth());
+    assertEquals(0, startEnd[0]);
+    assertEquals(25, startEnd[1]);
+
+    hc.hideColumns(0, 0);
+    startEnd = hc.getVisibleStartAndEndIndex(align.getWidth());
+    assertEquals(1, startEnd[0]);
+    assertEquals(25, startEnd[1]);
+
+    hc.hideColumns(6, 9);
+    hc.hideColumns(11, 12);
+    startEnd = hc.getVisibleStartAndEndIndex(align.getWidth());
+    assertEquals(1, startEnd[0]);
+    assertEquals(25, startEnd[1]);
+
+    hc.hideColumns(24, 25);
+    startEnd = hc.getVisibleStartAndEndIndex(align.getWidth());
+    System.out.println(startEnd[0] + " : " + startEnd[1]);
+    assertEquals(1, startEnd[0]);
+    assertEquals(23, startEnd[1]);
+  }
+
+  @Test(groups = "Functional")
+  public void testGetRegionWithEdgeAtRes()
+  {
+    HiddenColumns hc = new HiddenColumns();
+
+    int[] result = hc.getRegionWithEdgeAtRes(5);
+    assertNull(result);
+
+    hc.hideColumns(3, 7);
+    hc.hideColumns(10, 10);
+    hc.hideColumns(14, 15);
+
+    result = hc.getRegionWithEdgeAtRes(3);
+    assertEquals(3, result[0]);
+    assertEquals(7, result[1]);
+
+    result = hc.getRegionWithEdgeAtRes(5);
+    assertEquals(10, result[0]);
+    assertEquals(10, result[1]);
+
+    result = hc.getRegionWithEdgeAtRes(6);
+    assertNull(result);
+  }
+
+  @Test(groups = "Functional")
+  public void testPropagateInsertions()
+  {
+    // create an alignment with no gaps - this will be the profile seq and other
+    // JPRED seqs
+    AlignmentGenerator gen = new AlignmentGenerator(false);
+    AlignmentI al = gen.generate(20, 10, 1234, 0, 0);
+
+    // get the profileseq
+    SequenceI profileseq = al.getSequenceAt(0);
+    SequenceI gappedseq = new Sequence(profileseq);
+    gappedseq.insertCharAt(5, al.getGapCharacter());
+    gappedseq.insertCharAt(6, al.getGapCharacter());
+    gappedseq.insertCharAt(7, al.getGapCharacter());
+    gappedseq.insertCharAt(8, al.getGapCharacter());
+    
+    // create an alignment view with the gapped sequence
+    SequenceI[] seqs = new SequenceI[1];
+    seqs[0] = gappedseq;
+    AlignmentI newal = new Alignment(seqs);
+    HiddenColumns hidden = new HiddenColumns();
+    hidden.hideColumns(15, 17);
+
+    AlignmentView view = new AlignmentView(newal, hidden, null, true, false,
+            false);
+
+    // confirm that original contigs are as expected
+    int[] oldcontigs = hidden.getVisibleContigs(0, 20);
+    int[] testcontigs = { 0, 14, 18, 19 };
+    assertTrue(Arrays.equals(oldcontigs, testcontigs));
+            
+    // propagate insertions
+    HiddenColumns result = HiddenColumns.propagateInsertions(profileseq, al,
+            view);
+
+    // confirm that the contigs have changed to account for the gaps
+    int[] newcontigs = result.getVisibleContigs(0, 20);
+    testcontigs[1] = 10;
+    testcontigs[2] = 14;
+    assertTrue(Arrays.equals(newcontigs, testcontigs));
+    
+  }
 }
index 7795988..11b993d 100644 (file)
@@ -181,7 +181,8 @@ public class HiddenSequencesTest
   {
     AlignmentI al = new Alignment(seqs);
     HiddenSequences hs = al.getHiddenSequences();
-    for (int i = 0; i < SEQ_COUNT; i++)
+    int height = al.getHeight();
+    for (int i = 0; i < height; i++)
     {
       assertEquals(i, hs.findIndexWithoutHiddenSeqs(i));
     }
@@ -194,7 +195,7 @@ public class HiddenSequencesTest
     /*
      * alignment is now seq0/2/3/4/7/8/9
      */
-    assertEquals(SEQ_COUNT - 3, al.getHeight());
+    assertEquals(height - 3, al.getHeight());
     assertEquals(0, hs.findIndexWithoutHiddenSeqs(0));
     assertEquals(0, hs.findIndexWithoutHiddenSeqs(1));
     assertEquals(1, hs.findIndexWithoutHiddenSeqs(2));
@@ -205,6 +206,19 @@ public class HiddenSequencesTest
     assertEquals(4, hs.findIndexWithoutHiddenSeqs(7));
     assertEquals(5, hs.findIndexWithoutHiddenSeqs(8));
     assertEquals(6, hs.findIndexWithoutHiddenSeqs(9));
+
+    /*
+     * hide first two sequences
+     */
+    hs.showAll(null);
+    hs.hideSequence(seqs[0]);
+    hs.hideSequence(seqs[1]);
+    assertEquals(-1, hs.findIndexWithoutHiddenSeqs(0));
+    assertEquals(-1, hs.findIndexWithoutHiddenSeqs(1));
+    for (int i = 2; i < height; i++)
+    {
+      assertEquals(i - 2, hs.findIndexWithoutHiddenSeqs(i));
+    }
   }
 
   /**
index d105cc5..fbeb365 100644 (file)
@@ -42,7 +42,7 @@ public class SequenceFeatureTest
   }
 
   @Test(groups = { "Functional" })
-  public void testCopyConstructor()
+  public void testCopyConstructors()
   {
     SequenceFeature sf1 = new SequenceFeature("type", "desc", 22, 33,
             12.5f, "group");
@@ -56,10 +56,41 @@ public class SequenceFeatureTest
     assertEquals("desc", sf2.getDescription());
     assertEquals(22, sf2.getBegin());
     assertEquals(33, sf2.getEnd());
+    assertEquals(12.5f, sf2.getScore());
     assertEquals("+", sf2.getValue("STRAND"));
     assertEquals("Testing", sf2.getValue("Note"));
     // shallow clone of otherDetails map - contains the same object values!
     assertSame(count, sf2.getValue("Count"));
+
+    /*
+     * copy constructor modifying begin/end/group/score
+     */
+    SequenceFeature sf3 = new SequenceFeature(sf1, 11, 14, "group2", 17.4f);
+    assertEquals("type", sf3.getType());
+    assertEquals("desc", sf3.getDescription());
+    assertEquals(11, sf3.getBegin());
+    assertEquals(14, sf3.getEnd());
+    assertEquals(17.4f, sf3.getScore());
+    assertEquals("+", sf3.getValue("STRAND"));
+    assertEquals("Testing", sf3.getValue("Note"));
+    // shallow clone of otherDetails map - contains the same object values!
+    assertSame(count, sf3.getValue("Count"));
+
+    /*
+     * copy constructor modifying type/begin/end/group/score
+     */
+    SequenceFeature sf4 = new SequenceFeature(sf1, "Disulfide bond", 12,
+            15, "group3", -9.1f);
+    assertEquals("Disulfide bond", sf4.getType());
+    assertTrue(sf4.isContactFeature());
+    assertEquals("desc", sf4.getDescription());
+    assertEquals(12, sf4.getBegin());
+    assertEquals(15, sf4.getEnd());
+    assertEquals(-9.1f, sf4.getScore());
+    assertEquals("+", sf4.getValue("STRAND"));
+    assertEquals("Testing", sf4.getValue("Note"));
+    // shallow clone of otherDetails map - contains the same object values!
+    assertSame(count, sf4.getValue("Count"));
   }
 
   /**
index f6d4028..b0af5c8 100644 (file)
@@ -9,10 +9,12 @@ import static org.testng.Assert.assertSame;
 import static org.testng.Assert.assertTrue;
 import static org.testng.Assert.fail;
 
+import jalview.analysis.Conservation;
 import jalview.schemes.NucleotideColourScheme;
 import jalview.schemes.PIDColourScheme;
 
 import java.awt.Color;
+import java.util.Collections;
 
 import junit.extensions.PA;
 
@@ -232,6 +234,11 @@ public class SequenceGroupTest
     sg.setName("g1");
     sg.setDescription("desc");
     sg.setColourScheme(new PIDColourScheme());
+    Conservation cons = new Conservation("Cons", 2,
+            Collections.<SequenceI> emptyList(), 3, 12);
+    PA.setValue(cons, "consSequence", new Sequence("s", "abc"));
+    sg.getGroupColourScheme().setConservation(cons);
+    sg.getGroupColourScheme().setConsensus(new Profiles(null));
     sg.setDisplayBoxes(false);
     sg.setDisplayText(false);
     sg.setColourText(true);
@@ -255,6 +262,10 @@ public class SequenceGroupTest
     assertEquals(sg2.getDescription(), sg.getDescription());
     assertNotSame(sg2.getGroupColourScheme(), sg.getGroupColourScheme());
     assertSame(sg2.getColourScheme(), sg.getColourScheme());
+    assertSame(PA.getValue(sg2.getGroupColourScheme(), "consensus"),
+            PA.getValue(sg.getGroupColourScheme(), "consensus"));
+    assertSame(PA.getValue(sg2.getGroupColourScheme(), "conservation"),
+            PA.getValue(sg.getGroupColourScheme(), "conservation"));
     assertEquals(sg2.getDisplayBoxes(), sg.getDisplayBoxes());
     assertEquals(sg2.getDisplayText(), sg.getDisplayText());
     assertEquals(sg2.getColourText(), sg.getColourText());
index fed5992..67098ae 100644 (file)
@@ -86,12 +86,12 @@ public class AlignFrameTest
     assertFalse(alignFrame.hideFeatureColumns("exon", true));
     assertTrue(alignFrame.getViewport().getColumnSelection().isEmpty());
     assertTrue(alignFrame.getViewport().getAlignment().getHiddenColumns()
-            .getHiddenRegions()
+            .getHiddenColumnsCopy()
             .isEmpty());
     assertFalse(alignFrame.hideFeatureColumns("exon", false));
     assertTrue(alignFrame.getViewport().getColumnSelection().isEmpty());
     assertTrue(alignFrame.getViewport().getAlignment().getHiddenColumns()
-            .getHiddenRegions()
+            .getHiddenColumnsCopy()
             .isEmpty());
 
     /*
@@ -101,7 +101,7 @@ public class AlignFrameTest
     assertTrue(alignFrame.getViewport().getColumnSelection().isEmpty());
     List<int[]> hidden = alignFrame.getViewport().getAlignment()
             .getHiddenColumns()
-            .getHiddenRegions();
+            .getHiddenColumnsCopy();
     assertTrue(hidden.isEmpty());
 
     /*
@@ -111,7 +111,7 @@ public class AlignFrameTest
      */
     assertTrue(alignFrame.hideFeatureColumns("Turn", true));
     hidden = alignFrame.getViewport().getAlignment().getHiddenColumns()
-            .getHiddenRegions();
+            .getHiddenColumnsCopy();
     assertEquals(hidden.size(), 2);
     assertEquals(hidden.get(0)[0], 1);
     assertEquals(hidden.get(0)[1], 3);
index 4660842..b2286e0 100644 (file)
@@ -46,6 +46,7 @@ import jalview.schemes.ColourSchemeI;
 import jalview.schemes.PIDColourScheme;
 import jalview.structure.StructureSelectionManager;
 import jalview.util.MapList;
+import jalview.viewmodel.ViewportRanges;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -450,6 +451,65 @@ public class AlignViewportTest
     }
     ;
     Assert.assertEquals(c, 1, "Expected to find one occupancy row.");
+  }
 
+  @Test(groups = { "Functional" })
+  public void testGetConsensusSeq()
+  {
+    /*
+     * A-C
+     * A-C
+     * A-D
+     * --D
+     * consensus expected to be A-C
+     */
+    String fasta = ">s1\nA-C\n>s2\nA-C\n>s3\nA-D\n>s4\n--D\n";
+    AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(fasta,
+            DataSourceType.PASTE);
+    AlignViewport testme = af.getViewport();
+    SequenceI cons = testme.getConsensusSeq();
+    assertEquals("A-C", cons.getSequenceAsString());
+  }
+
+  @Test(groups = { "Functional" })
+  public void testHideRevealSequences()
+  {
+    ViewportRanges ranges = testee.getRanges();
+    assertEquals(3, al.getHeight());
+    assertEquals(0, ranges.getStartSeq());
+    assertEquals(2, ranges.getEndSeq());
+
+    /*
+     * hide first sequence
+     */
+    testee.hideSequence(new SequenceI[] { al.getSequenceAt(0) });
+    assertEquals(2, al.getHeight());
+    assertEquals(0, ranges.getStartSeq());
+    assertEquals(1, ranges.getEndSeq());
+
+    /*
+     * reveal hidden sequences above the first
+     */
+    testee.showSequence(0);
+    assertEquals(3, al.getHeight());
+    assertEquals(0, ranges.getStartSeq());
+    assertEquals(2, ranges.getEndSeq());
+
+    /*
+     * hide first and third sequences
+     */
+    testee.hideSequence(new SequenceI[] { al.getSequenceAt(0),
+        al.getSequenceAt(2) });
+    assertEquals(1, al.getHeight());
+    assertEquals(0, ranges.getStartSeq());
+    assertEquals(0, ranges.getEndSeq());
+
+    /*
+     * reveal all hidden sequences
+     */
+    testee.showAllHiddenSeqs();
+    assertEquals(3, al.getHeight());
+    assertEquals(0, ranges.getStartSeq());
+    assertEquals(2, ranges.getEndSeq());
   }
 }
index 17982be..158c901 100644 (file)
@@ -75,11 +75,11 @@ public class JSONFileTest
 
   private Alignment alignment;
 
-  private HashMap<String, SequenceI> expectedSeqs = new HashMap<String, SequenceI>();
+  private HashMap<String, SequenceI> expectedSeqs = new HashMap<>();
 
-  private HashMap<String, AlignmentAnnotation> expectedAnnots = new HashMap<String, AlignmentAnnotation>();
+  private HashMap<String, AlignmentAnnotation> expectedAnnots = new HashMap<>();
 
-  private HashMap<String, SequenceGroup> expectedGrps = new HashMap<String, SequenceGroup>();
+  private HashMap<String, SequenceGroup> expectedGrps = new HashMap<>();
 
   private HiddenColumns expectedColSel = new HiddenColumns();
 
@@ -141,7 +141,7 @@ public class JSONFileTest
     }
 
     // create and add a sequence group
-    List<SequenceI> grpSeqs = new ArrayList<SequenceI>();
+    List<SequenceI> grpSeqs = new ArrayList<>();
     grpSeqs.add(seqs[1]);
     grpSeqs.add(seqs[2]);
     grpSeqs.add(seqs[3]);
@@ -205,7 +205,7 @@ public class JSONFileTest
     TEST_SEQ_HEIGHT = expectedSeqs.size();
     TEST_GRP_HEIGHT = expectedGrps.size();
     TEST_ANOT_HEIGHT = expectedAnnots.size();
-    TEST_CS_HEIGHT = expectedColSel.getHiddenRegions().size();
+    TEST_CS_HEIGHT = expectedColSel.getHiddenColumnsCopy().size();
 
     exportSettings = new AlignExportSettingI()
     {
@@ -325,11 +325,11 @@ public class JSONFileTest
   {
     HiddenColumns cs = testJsonFile.getHiddenColumns();
     Assert.assertNotNull(cs);
-    Assert.assertNotNull(cs.getHiddenRegions());
-    List<int[]> hiddenCols = cs.getHiddenRegions();
+    Assert.assertNotNull(cs.getHiddenColumnsCopy());
+    List<int[]> hiddenCols = cs.getHiddenColumnsCopy();
     Assert.assertEquals(hiddenCols.size(), TEST_CS_HEIGHT);
     Assert.assertEquals(hiddenCols.get(0), expectedColSel
-            .getHiddenRegions().get(0),
+            .getHiddenColumnsCopy().get(0),
             "Mismatched hidden columns!");
   }
 
index 28d608f..7fd7abc 100644 (file)
@@ -448,4 +448,86 @@ public class FeatureColourFinderTest
     FeatureColourFinder finder2 = new FeatureColourFinder(null);
     assertTrue(finder2.noFeaturesDisplayed());
   }
+
+  @Test(groups = "Functional")
+  public void testFindFeatureColour_graduatedWithThreshold()
+  {
+    String kdFeature = "kd";
+    String metalFeature = "Metal";
+    seq.addSequenceFeature(new SequenceFeature(kdFeature, "hydrophobicity", 2,
+            2, 0f, "KdGroup"));
+    seq.addSequenceFeature(new SequenceFeature(kdFeature, "hydrophobicity", 4,
+            4, 5f, "KdGroup"));
+    seq.addSequenceFeature(new SequenceFeature(metalFeature, "Fe", 4, 4,
+            5f, "MetalGroup"));
+    seq.addSequenceFeature(new SequenceFeature(kdFeature, "hydrophobicity", 7,
+            7, 10f, "KdGroup"));
+  
+    /*
+     * kd feature has graduated colour from 0 to 10
+     * above threshold value of 5
+     */
+    Color min = new Color(100, 50, 150);
+    Color max = new Color(200, 0, 100);
+    FeatureColourI fc = new FeatureColour(min, max, 0, 10);
+    fc.setAboveThreshold(true);
+    fc.setThreshold(5f);
+    fr.setColour(kdFeature, fc);
+    FeatureColour green = new FeatureColour(Color.green);
+    fr.setColour(metalFeature, green);
+    fr.featuresAdded();
+
+    /*
+     * render order is kd above Metal
+     */
+    Object[][] data = new Object[2][];
+    data[0] = new Object[] { kdFeature, fc, true };
+    data[1] = new Object[] { metalFeature, green, true };
+    fr.setFeaturePriority(data);
+
+    av.setShowSequenceFeatures(true);
+  
+    /*
+     * position 2, column 1, score 0 - below threshold - default colour
+     */
+    Color c = finder.findFeatureColour(Color.blue, seq, 1);
+    assertEquals(c, Color.blue);
+
+    /*
+     * position 4, column 3, score 5 - at threshold
+     * should return Green (colour of Metal feature)
+     */
+    c = finder.findFeatureColour(Color.blue, seq, 3);
+    assertEquals(c, Color.green);
+  
+    /*
+     * position 7, column 9, score 10 - maximum colour in range
+     */
+    c = finder.findFeatureColour(Color.blue, seq, 9);
+    assertEquals(c, max);
+
+    /*
+     * now colour below threshold of 5
+     */
+    fc.setBelowThreshold(true);
+
+    /*
+     * position 2, column 1, score 0 - min colour
+     */
+    c = finder.findFeatureColour(Color.blue, seq, 1);
+    assertEquals(c, min);
+
+    /*
+     * position 4, column 3, score 5 - at threshold
+     * should return Green (colour of Metal feature)
+     */
+    c = finder.findFeatureColour(Color.blue, seq, 3);
+    assertEquals(c, Color.green);
+
+    /*
+     * position 7, column 9, score 10 - above threshold - default colour
+     */
+    c = finder.findFeatureColour(Color.blue, seq, 9);
+    assertEquals(c, Color.blue);
+  }
 }
index 3e27aba..dc86605 100644 (file)
@@ -267,15 +267,20 @@ public class FeatureRendererTest
     fr.filterFeaturesForDisplay(features, null); // empty list, does nothing
 
     SequenceI seq = av.getAlignment().getSequenceAt(0);
-    SequenceFeature sf1 = new SequenceFeature("Cath", "", 6, 8, "group1");
+    SequenceFeature sf1 = new SequenceFeature("Cath", "", 6, 8, Float.NaN,
+            "group1");
     seq.addSequenceFeature(sf1);
-    SequenceFeature sf2 = new SequenceFeature("Cath", "", 5, 11, "group2");
+    SequenceFeature sf2 = new SequenceFeature("Cath", "", 5, 11, 2f,
+            "group2");
     seq.addSequenceFeature(sf2);
-    SequenceFeature sf3 = new SequenceFeature("Cath", "", 5, 11, "group3");
+    SequenceFeature sf3 = new SequenceFeature("Cath", "", 5, 11, 3f,
+            "group3");
     seq.addSequenceFeature(sf3);
-    SequenceFeature sf4 = new SequenceFeature("Cath", "", 6, 8, "group4");
+    SequenceFeature sf4 = new SequenceFeature("Cath", "", 6, 8, 4f,
+            "group4");
     seq.addSequenceFeature(sf4);
-    SequenceFeature sf5 = new SequenceFeature("Cath", "", 6, 9, "group4");
+    SequenceFeature sf5 = new SequenceFeature("Cath", "", 6, 9, 5f,
+            "group4");
     seq.addSequenceFeature(sf5);
 
     fr.findAllFeatures(true);
@@ -329,6 +334,7 @@ public class FeatureRendererTest
 
     /*
      * no filtering of co-located features with graduated colour scheme
+     * filterFeaturesForDisplay does _not_ check colour threshold
      * sf2 is removed as its group is hidden
      */
     features = seq.getSequenceFeatures();
diff --git a/test/jalview/schemes/BuriedColourSchemeTest.java b/test/jalview/schemes/BuriedColourSchemeTest.java
new file mode 100644 (file)
index 0000000..cdb0e80
--- /dev/null
@@ -0,0 +1,33 @@
+package jalview.schemes;
+
+import static org.testng.Assert.assertEquals;
+
+import java.awt.Color;
+
+import org.testng.annotations.Test;
+
+public class BuriedColourSchemeTest
+{
+  /**
+   * Turn colours are based on the scores in ResidueProperties.buried A = 1.7, R
+   * = 0.1, N = 0.4, D = 0.4... min = 0.05 max = 4.6
+   * <p>
+   * scores are scaled to c 0-1 between min and max and colour is (0, 1-c, c)
+   */
+  @Test(groups = "Functional")
+  public void testFindColour()
+  {
+    ScoreColourScheme scheme = new BuriedColourScheme();
+
+    float min = 0.05f;
+    float max = 4.6f;
+    float a = (1.7f - min) / (max - min);
+    assertEquals(scheme.findColour('A', 0, null), new Color(0, 1 - a, a));
+
+    float d = (0.4f - min) / (max - min);
+    assertEquals(scheme.findColour('D', 0, null), new Color(0, 1 - d, d));
+
+    assertEquals(scheme.findColour('-', 0, null), Color.WHITE);
+  }
+
+}
index 1beca80..7a72c15 100644 (file)
@@ -22,6 +22,7 @@ package jalview.schemes;
 
 import static org.testng.AssertJUnit.assertEquals;
 import static org.testng.AssertJUnit.assertFalse;
+import static org.testng.AssertJUnit.assertNull;
 import static org.testng.AssertJUnit.assertTrue;
 import static org.testng.AssertJUnit.fail;
 
@@ -84,62 +85,6 @@ public class FeatureColourTest
   }
 
   @Test(groups = { "Functional" })
-  public void testIsColored_simpleColour()
-  {
-    FeatureColour fc = new FeatureColour(Color.RED);
-    assertTrue(fc
-            .isColored(new SequenceFeature("Cath", "", 1, 2, 0f, null)));
-  }
-
-  @Test(groups = { "Functional" })
-  public void testIsColored_colourByLabel()
-  {
-    FeatureColour fc = new FeatureColour();
-    fc.setColourByLabel(true);
-    assertTrue(fc
-            .isColored(new SequenceFeature("Cath", "", 1, 2, 0f, null)));
-  }
-
-  @Test(groups = { "Functional" })
-  public void testIsColored_aboveThreshold()
-  {
-    // graduated colour range from score 20 to 100
-    FeatureColour fc = new FeatureColour(Color.WHITE, Color.BLACK, 20f,
-            100f);
-
-    // score 0 is adjusted to bottom of range
-    SequenceFeature sf = new SequenceFeature("type", "desc", 0, 20, 0f,
-            null);
-    assertTrue(fc.isColored(sf));
-    assertEquals(Color.WHITE, fc.getColor(sf));
-
-    // score 120 is adjusted to top of range
-    sf = new SequenceFeature(sf, sf.getBegin(), sf.getEnd(),
-            sf.getFeatureGroup(), 120f);
-    assertEquals(Color.BLACK, fc.getColor(sf));
-
-    // value below threshold is still rendered
-    // setting threshold has no effect yet...
-    fc.setThreshold(60f);
-    sf = new SequenceFeature(sf, sf.getBegin(), sf.getEnd(),
-            sf.getFeatureGroup(), 36f);
-    assertTrue(fc.isColored(sf));
-    assertEquals(new Color(204, 204, 204), fc.getColor(sf));
-
-    // now apply threshold:
-    fc.setAboveThreshold(true);
-    assertFalse(fc.isColored(sf));
-    // colour is still returned though ?!?
-    assertEquals(new Color(204, 204, 204), fc.getColor(sf));
-
-    sf = new SequenceFeature(sf, sf.getBegin(), sf.getEnd(),
-            sf.getFeatureGroup(), 84f);
-    // above threshold now
-    assertTrue(fc.isColored(sf));
-    assertEquals(new Color(51, 51, 51), fc.getColor(sf));
-  }
-
-  @Test(groups = { "Functional" })
   public void testGetColor_simpleColour()
   {
     FeatureColour fc = new FeatureColour(Color.RED);
@@ -176,20 +121,35 @@ public class FeatureColourTest
   }
 
   @Test(groups = { "Functional" })
-  public void testGetColor_belowThreshold()
+  public void testGetColor_aboveBelowThreshold()
   {
     // gradient from [50, 150] from WHITE(255, 255, 255) to BLACK(0, 0, 0)
     FeatureColour fc = new FeatureColour(Color.WHITE, Color.BLACK, 50f,
             150f);
     SequenceFeature sf = new SequenceFeature("type", "desc", 0, 20, 70f,
             null);
+
+    /*
+     * feature with score of Float.NaN is always assigned minimum colour
+     */
+    SequenceFeature sf2 = new SequenceFeature("type", "desc", 0, 20,
+            Float.NaN, null);
+
     fc.setThreshold(100f); // ignore for now
-    assertTrue(fc.isColored(sf));
     assertEquals(new Color(204, 204, 204), fc.getColor(sf));
+    assertEquals(Color.white, fc.getColor(sf2));
 
     fc.setAboveThreshold(true); // feature lies below threshold
-    assertFalse(fc.isColored(sf));
-    assertEquals(new Color(204, 204, 204), fc.getColor(sf));
+    assertNull(fc.getColor(sf));
+    assertEquals(Color.white, fc.getColor(sf2));
+
+    fc.setBelowThreshold(true);
+    fc.setThreshold(70f);
+    assertNull(fc.getColor(sf)); // feature score == threshold - hidden
+    assertEquals(Color.white, fc.getColor(sf2));
+    fc.setThreshold(69f);
+    assertNull(fc.getColor(sf)); // feature score > threshold - hidden
+    assertEquals(Color.white, fc.getColor(sf2));
   }
 
   /**
diff --git a/test/jalview/schemes/HelixColourSchemeTest.java b/test/jalview/schemes/HelixColourSchemeTest.java
new file mode 100644 (file)
index 0000000..ed46419
--- /dev/null
@@ -0,0 +1,33 @@
+package jalview.schemes;
+
+import static org.testng.Assert.assertEquals;
+
+import java.awt.Color;
+
+import org.testng.annotations.Test;
+
+public class HelixColourSchemeTest
+{
+  /**
+   * Turn colours are based on the scores in ResidueProperties.helix A = 1.42, R
+   * = 0.98, N = 0.67, D = 1.01... min = 0.57 max = 1.51
+   * <p>
+   * scores are scaled to c 0-1 between min and max and colour is (c, 1-c, c)
+   */
+  @Test(groups = "Functional")
+  public void testFindColour()
+  {
+    ScoreColourScheme scheme = new HelixColourScheme();
+
+    float min = 0.57f;
+    float max = 1.51f;
+    float a = (1.42f - min) / (max - min);
+    assertEquals(scheme.findColour('A', 0, null), new Color(a, 1 - a, a));
+
+    float d = (1.01f - min) / (max - min);
+    assertEquals(scheme.findColour('D', 0, null), new Color(d, 1 - d, d));
+
+    assertEquals(scheme.findColour('-', 0, null), Color.WHITE);
+  }
+
+}
diff --git a/test/jalview/schemes/HydrophobicColourSchemeTest.java b/test/jalview/schemes/HydrophobicColourSchemeTest.java
new file mode 100644 (file)
index 0000000..f3b93a9
--- /dev/null
@@ -0,0 +1,35 @@
+package jalview.schemes;
+
+import static org.testng.Assert.assertEquals;
+
+import java.awt.Color;
+
+import org.testng.annotations.Test;
+
+public class HydrophobicColourSchemeTest
+{
+  /**
+   * Turn colours are based on the scores in ResidueProperties.hyd A = 1.8, R =
+   * -4.5, N = -3.5, D = -3.5... min = -3.9 max = 4.5
+   * <p>
+   * scores are scaled to c 0-1 between min and max and colour is (c, 0, 1-c)
+   */
+  @Test(groups = "Functional")
+  public void testFindColour()
+  {
+    ScoreColourScheme scheme = new HydrophobicColourScheme();
+
+    float min = -3.9f;
+    float max = 4.5f;
+    float a = (1.8f - min) / (max - min);
+    assertEquals(scheme.findColour('A', 0, null),
+ new Color(a, 0, 1 - a));
+
+    float d = (-3.5f - min) / (max - min);
+    assertEquals(scheme.findColour('D', 0, null),
+ new Color(d, 0, 1 - d));
+
+    assertEquals(scheme.findColour('-', 0, null), Color.WHITE);
+  }
+
+}
diff --git a/test/jalview/schemes/StrandColourSchemeTest.java b/test/jalview/schemes/StrandColourSchemeTest.java
new file mode 100644 (file)
index 0000000..4a8d0e9
--- /dev/null
@@ -0,0 +1,35 @@
+package jalview.schemes;
+
+import static org.testng.Assert.assertEquals;
+
+import java.awt.Color;
+
+import org.testng.annotations.Test;
+
+public class StrandColourSchemeTest
+{
+  /**
+   * Turn colours are based on the scores in ResidueProperties.strand A = 0.83,
+   * R = 0.93, N = 0.89, D = 0.54... min = 0.37 max = 1.7
+   * <p>
+   * scores are scaled to c 0-1 between min and max and colour is (c, c, 1-c)
+   */
+  @Test(groups = "Functional")
+  public void testFindColour()
+  {
+    ScoreColourScheme scheme = new StrandColourScheme();
+
+    float min = 0.37f;
+    float max = 1.7f;
+    float a = (0.83f - min) / (max - min);
+    assertEquals(scheme.findColour('A', 0, null),
+ new Color(a, a, 1 - a));
+
+    float d = (0.54f - min) / (max - min);
+    assertEquals(scheme.findColour('D', 0, null),
+ new Color(d, d, 1 - d));
+
+    assertEquals(scheme.findColour('-', 0, null), Color.WHITE);
+  }
+
+}
diff --git a/test/jalview/schemes/TurnColourSchemeTest.java b/test/jalview/schemes/TurnColourSchemeTest.java
new file mode 100644 (file)
index 0000000..5e6baf0
--- /dev/null
@@ -0,0 +1,35 @@
+package jalview.schemes;
+
+import static org.testng.Assert.assertEquals;
+
+import java.awt.Color;
+
+import org.testng.annotations.Test;
+
+public class TurnColourSchemeTest
+{
+  /**
+   * Turn colours are based on the scores in ResidueProperties.turn A = 0.66, R
+   * = 0.95, N = 1.56, D = 1.46... min = 0.47 max = 1.56
+   * <p>
+   * scores are scaled to c 0-1 between min and max and colour is (c, 1-c, 1-c)
+   */
+  @Test(groups = "Functional")
+  public void testFindColour()
+  {
+    ScoreColourScheme scheme = new TurnColourScheme();
+
+    float min = 0.47f;
+    float max = 1.56f;
+    float a = (0.66f - min) / (max - min);
+    assertEquals(scheme.findColour('A', 0, null),
+            new Color(a, 1 - a, 1 - a));
+
+    float d = (1.46f - min) / (max - min);
+    assertEquals(scheme.findColour('D', 0, null),
+            new Color(d, 1 - d, 1 - d));
+
+    assertEquals(scheme.findColour('-', 0, null), Color.WHITE);
+  }
+
+}
index 497014e..2a482ee 100644 (file)
@@ -54,6 +54,10 @@ public class UserColourSchemeTest
     assertEquals(c1, cs.findColour('h'));
     Color c2 = new Color(10, 20, 30);
     assertEquals(c2, cs.findColour('c'));
+    assertEquals(Color.WHITE, cs.findColour('G'));
+    assertEquals(Color.WHITE, cs.findColour('-'));
+    assertEquals(Color.WHITE, cs.findColour('.'));
+    assertEquals(Color.WHITE, cs.findColour(' '));
 
     cs = new UserColourScheme("white");
     cs.parseAppletParameter("D,E=red; K,R,H=0022FF; c=10 , 20,30;t=orange;lowercase=blue;s=pink");
@@ -80,4 +84,29 @@ public class UserColourSchemeTest
     String param = cs.toAppletParameter();
     assertEquals("D,E=ff0000;H,K,R=0022ff;c=0a141e", param);
   }
+
+  /**
+   * Test for user colour scheme constructed with a colour per residue,
+   * including gap. Note this can currently be done from the User Defined
+   * Colours dialog, but not by parsing a colours parameter, as
+   * parseAppletParameter only recognises amino acid codes.
+   */
+  @Test(groups = "Functional")
+  public void testConstructor_coloursArray()
+  {
+    Color g = Color.green;
+    Color y = Color.yellow;
+    Color b = Color.blue;
+    Color r = Color.red;
+    // colours for ARNDCQEGHILKMFPSTWYVBZ and gap
+    Color[] colours = new Color[] { g, y, b, r, g, y, r, b, g, y, r, b, g,
+        y, r, b, g, y, r, b, g, y, r, g };
+    UserColourScheme cs = new UserColourScheme(colours);
+
+    assertEquals(g, cs.findColour('A'));
+    assertEquals(b, cs.findColour('n'));
+    assertEquals(g, cs.findColour('-'));
+    assertEquals(g, cs.findColour('.'));
+    assertEquals(g, cs.findColour(' '));
+  }
 }
index 19c8438..d0ec3e8 100644 (file)
@@ -689,7 +689,7 @@ public class MappingUtilsTest
     AlignedCodonFrame acf3 = new AlignedCodonFrame();
     acf3.addMap(seq3.getDatasetSequence(), seq1.getDatasetSequence(), map);
 
-    List<AlignedCodonFrame> mappings = new ArrayList<AlignedCodonFrame>();
+    List<AlignedCodonFrame> mappings = new ArrayList<>();
     mappings.add(acf1);
     mappings.add(acf2);
     mappings.add(acf3);
@@ -764,7 +764,7 @@ public class MappingUtilsTest
     AlignedCodonFrame acf4 = new AlignedCodonFrame();
     acf4.addMap(seq3.getDatasetSequence(), seq4.getDatasetSequence(), map);
 
-    List<AlignedCodonFrame> mappings = new ArrayList<AlignedCodonFrame>();
+    List<AlignedCodonFrame> mappings = new ArrayList<>();
     mappings.add(acf1);
     mappings.add(acf2);
     mappings.add(acf3);
@@ -821,7 +821,7 @@ public class MappingUtilsTest
     AlignedCodonFrame acf = new AlignedCodonFrame();
     MapList map = new MapList(new int[] { 8, 16 }, new int[] { 5, 7 }, 3, 1);
     acf.addMap(dna.getDatasetSequence(), protein.getDatasetSequence(), map);
-    List<AlignedCodonFrame> mappings = new ArrayList<AlignedCodonFrame>();
+    List<AlignedCodonFrame> mappings = new ArrayList<>();
     mappings.add(acf);
 
     AlignmentI prot = new Alignment(new SequenceI[] { protein });
@@ -913,7 +913,7 @@ public class MappingUtilsTest
     MappingUtils.mapColumnSelection(proteinSelection, hiddenCols,
             proteinView, dnaView, dnaSelection, dnaHidden);
     assertEquals("[]", dnaSelection.getSelected().toString());
-    List<int[]> hidden = dnaHidden.getHiddenRegions();
+    List<int[]> hidden = dnaHidden.getHiddenColumnsCopy();
     assertEquals(1, hidden.size());
     assertEquals("[0, 4]", Arrays.toString(hidden.get(0)));
 
@@ -930,7 +930,7 @@ public class MappingUtilsTest
     proteinSelection.hideSelectedColumns(1, hiddenCols);
     MappingUtils.mapColumnSelection(proteinSelection, hiddenCols,
             proteinView, dnaView, dnaSelection, dnaHidden);
-    hidden = dnaHidden.getHiddenRegions();
+    hidden = dnaHidden.getHiddenColumnsCopy();
     assertEquals(1, hidden.size());
     assertEquals("[0, 3]", Arrays.toString(hidden.get(0)));
 
@@ -944,7 +944,7 @@ public class MappingUtilsTest
     proteinSelection.hideSelectedColumns(2, hiddenCols);
     MappingUtils.mapColumnSelection(proteinSelection, hiddenCols,
             proteinView, dnaView, dnaSelection, dnaHidden);
-    assertTrue(dnaHidden.getHiddenRegions().isEmpty());
+    assertTrue(dnaHidden.getHiddenColumnsCopy().isEmpty());
 
     /*
      * Column 3 in protein picks up Seq1/P, Seq2/Q, Seq3/S which map to columns
@@ -959,7 +959,7 @@ public class MappingUtilsTest
     MappingUtils.mapColumnSelection(proteinSelection, hiddenCols,
             proteinView, dnaView, dnaSelection, dnaHidden);
     assertEquals("[0, 1, 2, 3]", dnaSelection.getSelected().toString());
-    hidden = dnaHidden.getHiddenRegions();
+    hidden = dnaHidden.getHiddenColumnsCopy();
     assertEquals(1, hidden.size());
     assertEquals("[5, 10]", Arrays.toString(hidden.get(0)));
 
@@ -974,7 +974,7 @@ public class MappingUtilsTest
     proteinSelection.hideSelectedColumns(3, hiddenCols);
     MappingUtils.mapColumnSelection(proteinSelection, hiddenCols,
             proteinView, dnaView, dnaSelection, dnaHidden);
-    hidden = dnaHidden.getHiddenRegions();
+    hidden = dnaHidden.getHiddenColumnsCopy();
     assertEquals(2, hidden.size());
     assertEquals("[0, 3]", Arrays.toString(hidden.get(0)));
     assertEquals("[5, 10]", Arrays.toString(hidden.get(1)));
@@ -988,7 +988,7 @@ public class MappingUtilsTest
     /*
      * [start, end] ranges
      */
-    List<int[]> ranges = new ArrayList<int[]>();
+    List<int[]> ranges = new ArrayList<>();
     assertEquals(0, MappingUtils.getLength(ranges));
     ranges.add(new int[] { 1, 1 });
     assertEquals(1, MappingUtils.getLength(ranges));
@@ -1011,7 +1011,7 @@ public class MappingUtilsTest
   public void testContains()
   {
     assertFalse(MappingUtils.contains(null, 1));
-    List<int[]> ranges = new ArrayList<int[]>();
+    List<int[]> ranges = new ArrayList<>();
     assertFalse(MappingUtils.contains(ranges, 1));
 
     ranges.add(new int[] { 1, 4 });
index 636f8dd..74cd8f9 100644 (file)
@@ -1,6 +1,7 @@
 package jalview.viewmodel;
 
 import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
 import static org.testng.Assert.assertTrue;
 
 import jalview.analysis.AlignmentGenerator;
@@ -14,6 +15,7 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 
+import org.testng.annotations.BeforeClass;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
@@ -25,6 +27,14 @@ public class ViewportRangesTest {
 
   AlignmentI smallAl = gen.generate(7, 2, 2, 5, 5);
 
+  @BeforeClass(alwaysRun = true)
+  public void setUp()
+  {
+    gen = new AlignmentGenerator(false);
+    al = gen.generate(20, 30, 1, 5, 5);
+    smallAl = gen.generate(7, 2, 2, 5, 5);
+  }
+
   @BeforeMethod(alwaysRun = true)
   public void cleanUp()
   {
@@ -65,17 +75,6 @@ public class ViewportRangesTest {
   }
 
   @Test(groups = { "Functional" })
-  public void testSetEndRes()
-  {
-    ViewportRanges vr = new ViewportRanges(al);
-    vr.setEndRes(-1);
-    assertEquals(vr.getEndRes(), 0);
-
-    vr.setEndRes(al.getWidth() - 1);
-    assertEquals(vr.getEndRes(), al.getWidth() - 1);
-  }
-
-  @Test(groups = { "Functional" })
   public void testSetEndSeq()
   {
     ViewportRanges vr = new ViewportRanges(al);
@@ -85,7 +84,8 @@ public class ViewportRangesTest {
     vr.setEndSeq(al.getHeight());
     assertEquals(vr.getEndSeq(), al.getHeight() - 1);
 
-    vr.setEndRes(al.getHeight() - 1);
+    // vr.setEndRes(al.getHeight() - 1);
+    vr.setEndSeq(al.getHeight() - 1);
     assertEquals(vr.getEndSeq(), al.getHeight() - 1);
   }
 
@@ -347,17 +347,41 @@ public class ViewportRangesTest {
   @Test(groups = { "Functional" })
   public void testScrollToWrappedVisible()
   {
-    ViewportRanges vr = new ViewportRanges(al);
+    AlignmentI al2 = gen.generate(60, 30, 1, 5, 5);
+
+    ViewportRanges vr = new ViewportRanges(al2);
+
+    // start with viewport on 5-14
     vr.setViewportStartAndWidth(5, 10);
+    assertEquals(vr.getStartRes(), 5);
+    assertEquals(vr.getEndRes(), 14);
+
+    // scroll to 12 - no change
+    assertFalse(vr.scrollToWrappedVisible(12));
+    assertEquals(vr.getStartRes(), 5);
 
-    vr.scrollToWrappedVisible(0);
+    // scroll to 2 - back to 0-9
+    assertTrue(vr.scrollToWrappedVisible(2));
     assertEquals(vr.getStartRes(), 0);
+    assertEquals(vr.getEndRes(), 9);
 
-    vr.scrollToWrappedVisible(10);
-    assertEquals(vr.getStartRes(), 10);
+    // scroll to 9 - no change
+    assertFalse(vr.scrollToWrappedVisible(9));
+    assertEquals(vr.getStartRes(), 0);
 
-    vr.scrollToWrappedVisible(15);
+    // scroll to 12 - moves to 10-19
+    assertTrue(vr.scrollToWrappedVisible(12));
     assertEquals(vr.getStartRes(), 10);
+    assertEquals(vr.getEndRes(), 19);
+
+    vr.setStartRes(13);
+    assertEquals(vr.getStartRes(), 13);
+    assertEquals(vr.getEndRes(), 22);
+
+    // scroll to 45 - jumps to 43-52
+    assertTrue(vr.scrollToWrappedVisible(45));
+    assertEquals(vr.getStartRes(), 43);
+    assertEquals(vr.getEndRes(), 52);
   }
 
   // leave until JAL-2388 is merged and we can do without viewport
@@ -401,15 +425,6 @@ public class ViewportRangesTest {
     assertTrue(l.verify(0, emptylist));
     l.reset();
 
-    vr.setEndRes(10);
-    assertTrue(l.verify(1, Arrays.asList("startres")));
-    l.reset();
-
-    // no event fired for same value
-    vr.setEndRes(10);
-    assertTrue(l.verify(0, emptylist));
-    l.reset();
-
     vr.setStartSeq(4);
     assertTrue(l.verify(1, Arrays.asList("startseq")));
     l.reset();
@@ -523,6 +538,223 @@ public class ViewportRangesTest {
     assertTrue(l.verify(1, Arrays.asList("startres")));
     l.reset();
   }
+
+  @Test(groups = { "Functional" })
+  public void testGetWrappedScrollPosition()
+  {
+    AlignmentI al2 = gen.generate(157, 15, 1, 5, 5);
+    ViewportRanges vr = new ViewportRanges(al2);
+    vr.setStartEndRes(0, 39);
+    int width = vr.getViewportWidth(); // 40
+
+    /*
+     * scroll is 0 at column 0 (only)
+     */
+    assertEquals(vr.getWrappedScrollPosition(0), 0);
+
+    /*
+     * scroll is 1 at columns 1-40
+     */
+    int i = 1;
+    int j = width;
+    for (; i <= j; i++)
+    {
+      assertEquals(1, vr.getWrappedScrollPosition(i));
+    }
+
+    /*
+     * scroll is 2 at columns 41-80, etc
+     */
+    j += width;
+    for (; i <= j; i++)
+    {
+      assertEquals(2, vr.getWrappedScrollPosition(i), "For " + i);
+    }
+  }
+
+  @Test(groups = { "Functional" })
+  public void testPageUpDownWrapped()
+  {
+    /*
+     * 15 sequences, 110 residues wide (+gaps)
+     */
+    AlignmentI al2 = gen.generate(110, 15, 1, 5, 5);
+
+    ViewportRanges vr = new ViewportRanges(al2);
+    vr.setWrappedMode(true);
+
+    // first row
+    vr.setViewportStartAndWidth(0, 40);
+    int width = vr.getViewportWidth();
+    assertEquals(width, 40);
+    assertEquals(vr.getStartRes(), 0);
+    assertEquals(vr.getEndRes(), 39);
+    assertEquals(vr.getStartSeq(), 0);
+    assertEquals(vr.getEndSeq(), 14);
+
+    // second row
+    vr.pageDown();
+    assertEquals(vr.getStartRes(), 40);
+    assertEquals(vr.getEndRes(), 79);
+    assertEquals(vr.getStartSeq(), 0);
+    assertEquals(vr.getEndSeq(), 14);
+
+    // third and last row
+    // note endRes is nominal (>width) to preserve viewport width
+    vr.pageDown();
+    assertEquals(vr.getStartRes(), 80);
+    assertEquals(vr.getEndRes(), 119);
+    assertEquals(vr.getStartSeq(), 0);
+    assertEquals(vr.getEndSeq(), 14);
+
+    // another pageDown should do nothing
+    vr.pageDown();
+    assertEquals(vr.getStartRes(), 80);
+    assertEquals(vr.getEndRes(), 119);
+    assertEquals(vr.getStartSeq(), 0);
+    assertEquals(vr.getEndSeq(), 14);
+
+    // back to second row
+    vr.pageUp();
+    assertEquals(vr.getStartRes(), 40);
+    assertEquals(vr.getEndRes(), 79);
+    assertEquals(vr.getStartSeq(), 0);
+    assertEquals(vr.getEndSeq(), 14);
+
+    // back to first row
+    vr.pageUp();
+    assertEquals(vr.getStartRes(), 0);
+    assertEquals(vr.getEndRes(), 39);
+    assertEquals(vr.getStartSeq(), 0);
+    assertEquals(vr.getEndSeq(), 14);
+
+    // another pageUp should do nothing
+    vr.pageUp();
+    assertEquals(vr.getStartRes(), 0);
+    assertEquals(vr.getEndRes(), 39);
+    assertEquals(vr.getStartSeq(), 0);
+    assertEquals(vr.getEndSeq(), 14);
+
+    /*
+     * simulate scroll right a few positions
+     */
+    vr.setStartRes(5);
+    assertEquals(vr.getStartRes(), 5);
+    assertEquals(vr.getEndRes(), 5 + width - 1); // 44
+
+    vr.pageDown(); // 5-44 shifts to 45-84
+    assertEquals(vr.getStartRes(), 45);
+    assertEquals(vr.getEndRes(), 84);
+
+    vr.pageDown(); // 45-84 shifts to 85-124
+    assertEquals(vr.getStartRes(), 85);
+    assertEquals(vr.getEndRes(), 124);
+
+    vr.pageDown(); // no change - at end already
+    assertEquals(vr.getStartRes(), 85);
+    assertEquals(vr.getEndRes(), 124);
+
+    vr.pageUp(); // back we go
+    assertEquals(vr.getStartRes(), 45);
+    assertEquals(vr.getEndRes(), 84);
+
+    vr.pageUp();
+    assertEquals(vr.getStartRes(), 5);
+    assertEquals(vr.getEndRes(), 44);
+
+    vr.pageUp(); // back to the start
+    assertEquals(vr.getStartRes(), 0);
+    assertEquals(vr.getEndRes(), 39);
+  }
+
+  @Test(groups = { "Functional" })
+  public void testSetStartEndResWrapped()
+  {
+    ViewportRanges vr = new ViewportRanges(al);
+    vr.setWrappedMode(true);
+    vr.setStartEndRes(-1, -1);
+    assertEquals(vr.getStartRes(), 0);
+    assertEquals(vr.getEndRes(), 0);
+  
+    vr.setStartEndRes(5, 19);
+    assertEquals(vr.getStartRes(), 5);
+    assertEquals(vr.getEndRes(), 19);
+  
+    // bounds are not constrained to alignment width
+    // when in wrapped mode
+    vr.setStartEndRes(88, 888);
+    assertEquals(vr.getStartRes(), 88);
+    assertEquals(vr.getEndRes(), 888);
+  
+    ViewportRanges vrsmall = new ViewportRanges(smallAl);
+    vrsmall.setWrappedMode(true);
+    vrsmall.setStartEndRes(88, 888);
+    assertEquals(vrsmall.getStartRes(), 88);
+    assertEquals(vrsmall.getEndRes(), 888);
+  
+    // make visible alignment width = 0
+    smallAl.getHiddenColumns().hideColumns(0, 6);
+    vrsmall.setStartEndRes(0, 4);
+    assertEquals(vrsmall.getStartRes(), 0);
+    assertEquals(vrsmall.getEndRes(), 4);
+  }
+
+  @Test(groups = { "Functional" })
+  public void testSetViewportStartAndWidthWrapped()
+  {
+    ViewportRanges vr = new ViewportRanges(al);
+    vr.setWrappedMode(true);
+    vr.setViewportStartAndWidth(2, 6);
+    assertEquals(vr.getViewportWidth(), 6);
+    assertEquals(vr.getStartRes(), 2);
+  
+    // reset -ve values of start to 0
+    vr.setViewportStartAndWidth(-1, 7);
+    assertEquals(vr.getViewportWidth(), 7);
+    assertEquals(vr.getStartRes(), 0);
+  
+    // out of bounds values are not forced to within bounds
+    vr.setViewportStartAndWidth(35, 5);
+    assertEquals(vr.getViewportWidth(), 5);
+    assertEquals(vr.getStartRes(), 35);
+  
+    // small alignment doesn't get bounds reset
+    ViewportRanges vrsmall = new ViewportRanges(smallAl);
+    vrsmall.setViewportStartAndWidth(0, 63);
+    assertEquals(vrsmall.getViewportWidth(), 7);
+    assertEquals(vrsmall.getStartRes(), 0);
+  }
+
+  @Test(groups = { "Functional" })
+  public void testGetWrappedMaxScroll()
+  {
+    // generate an ungapped alignment of width 140
+    int alignmentWidth = 140;
+    AlignmentI al2 = gen.generate(alignmentWidth, 15, 1, 0, 5);
+    ViewportRanges vr = new ViewportRanges(al2);
+    vr.setStartEndRes(0, 39);
+    int width = vr.getViewportWidth(); // 40
+    int partWidth = alignmentWidth % width; // 20
+  
+    /*
+     * there are 3 * 40 remainder 20 residues
+     * number of widths depends on offset (scroll right)
+     * 4 widths (maxScroll = 3) if offset by 0 or more than 19 columns
+     * 5 widths (maxScroll = 4) if 1 <= offset <= 19
+     */
+    for (int col = 0; col < alignmentWidth; col++)
+    {
+      int offset = col % width;
+      if (offset > 0 && offset < partWidth)
+      {
+        assertEquals(vr.getWrappedMaxScroll(col), 4, "col " + col);
+      }
+      else
+      {
+        assertEquals(vr.getWrappedMaxScroll(col), 3, "col " + col);
+      }
+    }
+  }
 }
 
 // mock listener for property change events
index 3cfc2bb..557700a 100755 (executable)
@@ -1453,7 +1453,7 @@ and any path to a file to save to the file]]></string>
                                                                <string><![CDATA[664]]></string>
                                                        </property>
                                                        <property name="sourceName">
-                                                               <string><![CDATA[min-jabaws-client-2.1.0.jar]]></string>
+                                                               <string><![CDATA[jabaws-min-client-2.2.0.jar]]></string>
                                                        </property>
                                                        <property name="overrideUnixPermissions">
                                                                <boolean>false</boolean>