Merge branch 'develop' into features/JAL-2349_matrixvis
authorJim Procter <jprocter@issues.jalview.org>
Fri, 9 Jun 2017 14:33:17 +0000 (18:33 +0400)
committerJim Procter <jprocter@issues.jalview.org>
Fri, 9 Jun 2017 14:33:17 +0000 (18:33 +0400)
JAL-2349 update for refactor of hidden columns out of column selection

27 files changed:
1  2 
src/jalview/api/AlignViewportI.java
src/jalview/appletgui/#OverviewPanel.java#
src/jalview/appletgui/AnnotationPanel.java
src/jalview/datamodel/Alignment.java
src/jalview/datamodel/AlignmentAnnotation.java
src/jalview/datamodel/AlignmentI.java
src/jalview/datamodel/Profile.java~
src/jalview/datamodel/ProfileI.java~
src/jalview/datamodel/Profiles.java~
src/jalview/datamodel/ProfilesI.java~
src/jalview/datamodel/ResidueCount.java~
src/jalview/datamodel/SearchResultMatchI.java~
src/jalview/datamodel/SearchResultsI.java~
src/jalview/ext/jmol/JmolParser.java~
src/jalview/gui/#OverviewPanel.java#
src/jalview/gui/AnnotationPanel.java
src/jalview/gui/SeqCanvas.java.broken
src/jalview/gui/StructureChooser.java~
src/jalview/gui/TreeCanvas.java~
src/jalview/io/AppletFormatAdapter.java
src/jalview/renderer/AnnotationRenderer.java
src/jalview/renderer/ContactMapRenderer.java
src/jalview/renderer/api/AnnotationRowRendererI.java
src/jalview/structure/StructureSelectionManager.java~
src/jalview/util/SparseCount.java~
src/jalview/viewmodel/AlignmentViewport.java
src/jalview/ws/dbsources/Pdb.java~

@@@ -458,5 -472,26 +473,30 @@@ public interface AlignViewportI extend
     */
    SearchResultsI getSearchResults();
  
 +  ContactListI getContactList(AlignmentAnnotation _aa, int column);
++
+   /**
+    * Updates view settings with the given font. You may need to call
+    * AlignmentPanel.fontChanged to update the layout geometry.
+    * 
+    * @param setGrid
+    *          when true, charWidth/height is set according to font metrics
+    */
+   void setFont(Font newFont, boolean b);
+   /**
+    * Answers true if split screen protein and cDNA use the same font
+    * 
+    * @return
+    */
++  @Override
+   boolean isProteinFontAsCdna();
+   /**
+    * Set the flag for whether split screen protein and cDNA use the same font
+    * 
+    * @return
+    */
++  @Override
+   void setProteinFontAsCdna(boolean b);
  }
index 0000000,0000000..14c3158
new file mode 100755 (executable)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,453 @@@
++/*
++ * 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.appletgui;
++
++import jalview.datamodel.SequenceI;
++import jalview.viewmodel.OverviewDimensions;
++import jalview.datamodel.AlignmentI;
++import jalview.renderer.seqfeatures.FeatureColourFinder;
++
++import java.awt.Color;
++import java.awt.Dimension;
++import java.awt.Frame;
++import java.awt.Graphics;
++import java.awt.Image;
++import java.awt.Panel;
++import java.awt.event.ComponentAdapter;
++import java.awt.event.ComponentEvent;
++import java.awt.event.MouseEvent;
++import java.awt.event.MouseListener;
++import java.awt.event.MouseMotionListener;
++
++public class OverviewPanel extends Panel implements Runnable,
++        MouseMotionListener, MouseListener
++{
++  private OverviewDimensions od;
++
++  private Image miniMe;
++
++  private Image offscreen;
++
++  private AlignViewport av;
++
++  private AlignmentPanel ap;
++
++  private boolean resizing = false;
++
++  // This is set true if the user resizes whilst
++  // the overview is being calculated
++  private boolean resizeAgain = false;
++
++  // Can set different properties in this seqCanvas than
++  // main visible SeqCanvas
++  private SequenceRenderer sr;
++
++  private FeatureRenderer fr;
++
++  private Frame nullFrame;
++
++  public OverviewPanel(AlignmentPanel alPanel)
++  {
++    this.av = alPanel.av;
++    this.ap = alPanel;
++    setLayout(null);
++    nullFrame = new Frame();
++    nullFrame.addNotify();
++
++    sr = new SequenceRenderer(av);
++    sr.graphics = nullFrame.getGraphics();
++    sr.renderGaps = false;
++    sr.forOverview = true;
++    fr = new FeatureRenderer(av);
++
++    od = new OverviewDimensions(av.getRanges(), av.isShowAnnotation());
++
++    setSize(new Dimension(od.getWidth(), od.getHeight()));
++    addComponentListener(new ComponentAdapter()
++    {
++
++      @Override
++      public void componentResized(ComponentEvent evt)
++      {
++        if ((getWidth() != od.getWidth())
++                || (getHeight() != (od.getHeight())))
++        {
++          updateOverviewImage();
++        }
++      }
++    });
++
++    addMouseMotionListener(this);
++
++    addMouseListener(this);
++
++    updateOverviewImage();
++
++  }
++
++  @Override
++  public void mouseEntered(MouseEvent evt)
++  {
++  }
++
++  @Override
++  public void mouseExited(MouseEvent evt)
++  {
++  }
++
++  @Override
++  public void mouseClicked(MouseEvent evt)
++  {
++  }
++
++  @Override
++  public void mouseMoved(MouseEvent evt)
++  {
++  }
++
++  @Override
++  public void mousePressed(MouseEvent evt)
++  {
++    mouseAction(evt);
++  }
++
++  @Override
++  public void mouseReleased(MouseEvent evt)
++  {
++    mouseAction(evt);
++  }
++
++  @Override
++  public void mouseDragged(MouseEvent evt)
++  {
++    mouseAction(evt);
++  }
++
++  private void mouseAction(MouseEvent evt)
++  {
++    od.updateViewportFromMouse(evt.getX(), evt.getY(), av.getAlignment()
++            .getHiddenSequences(), av.getColumnSelection(), av
++            .getRanges());
++    ap.setScrollValues(od.getScrollCol(), od.getScrollRow());
++    ap.paintAlignment(false);
++  }
++
++  /**
++   * Updates the overview image when the related alignment panel is updated
++   */
++  public void updateOverviewImage()
++  {
++    if (resizing)
++    {
++      resizeAgain = true;
++      return;
++    }
++
++    if (av.isShowSequenceFeatures())
++    {
++      fr.transferSettings(ap.seqPanel.seqCanvas.fr);
++    }
++
++    resizing = true;
++
++    if ((getSize().width > 0) && (getSize().height > 0))
++    {
++      od.setWidth(getSize().width);
++      od.setHeight(getSize().height);
++    }
++    setSize(new Dimension(od.getWidth(), od.getHeight()));
++
++    Thread thread = new Thread(this);
++    thread.start();
++    repaint();
++  }
++
++  @Override
++  public void run()
++  {
++    miniMe = null;
++
++    if (av.isShowSequenceFeatures())
++    {
++      fr.transferSettings(ap.seqPanel.seqCanvas.getFeatureRenderer());
++    }
++
++    if (getSize().width > 0 && getSize().height > 0)
++    {
++      od.setWidth(getSize().width);
++      od.setHeight(getSize().height);
++    }
++
++    setSize(new Dimension(od.getWidth(), od.getHeight()));
++
++    miniMe = nullFrame.createImage(od.getWidth(), od.getHeight());
++    offscreen = nullFrame.createImage(od.getWidth(), od.getHeight());
++
++    Graphics mg = miniMe.getGraphics();
++<<<<<<< HEAD
++
++    int alwidth = av.getAlignment().getWidth();
++    int alheight = av.getAlignment().getAbsoluteHeight();
++    float sampleCol = alwidth / (float) od.getWidth();
++    float sampleRow = alheight / (float) od.getSequencesHeight();
++=======
++    float sampleCol = (float) alwidth / (float) width;
++    float sampleRow = (float) alheight / (float) sequencesHeight;
++
++    int lastcol = 0, lastrow = 0;
++    int xstart = 0, ystart = 0;
++    Color color = Color.yellow;
++    int row, col, sameRow = 0, sameCol = 0;
++    jalview.datamodel.SequenceI seq;
++    final boolean hasHiddenRows = av.hasHiddenRows(), hasHiddenCols = av
++            .hasHiddenColumns();
++    boolean hiddenRow = false;
++    AlignmentI alignment = av.getAlignment();
++
++    FeatureColourFinder finder = new FeatureColourFinder(fr);
++    for (row = 0; row <= sequencesHeight; row++)
++    {
++      if (resizeAgain)
++      {
++        break;
++      }
++      if ((int) (row * sampleRow) == lastrow)
++      {
++        sameRow++;
++        continue;
++      }
++
++      hiddenRow = false;
++      if (hasHiddenRows)
++      {
++        seq = alignment.getHiddenSequences().getHiddenSequence(lastrow);
++        if (seq == null)
++        {
++          int index = alignment.getHiddenSequences()
++                  .findIndexWithoutHiddenSeqs(lastrow);
++
++          seq = alignment.getSequenceAt(index);
++        }
++        else
++        {
++          hiddenRow = true;
++        }
++      }
++      else
++      {
++        seq = alignment.getSequenceAt(lastrow);
++      }
++
++      for (col = 0; col < width; col++)
++      {
++        if ((int) (col * sampleCol) == lastcol
++                && (int) (row * sampleRow) == lastrow)
++        {
++          sameCol++;
++          continue;
++        }
++
++        lastcol = (int) (col * sampleCol);
++
++        if (seq.getLength() > lastcol)
++        {
++          color = sr.getResidueColour(seq, lastcol, finder);
++        }
++        else
++        {
++          color = Color.white;
++        }
++
++        if (hiddenRow
++                || (hasHiddenCols && !av.getColumnSelection().isVisible(
++                        lastcol)))
++        {
++          color = color.darker().darker();
++        }
++
++        mg.setColor(color);
++        if (sameCol == 1 && sameRow == 1)
++        {
++          mg.drawLine(xstart, ystart, xstart, ystart);
++        }
++        else
++        {
++          mg.fillRect(xstart, ystart, sameCol, sameRow);
++        }
++>>>>>>> bug/JAL-2436featureRendererThreading
++
++    buildImage(sampleRow, sampleCol, mg);
++
++    if (av.isShowAnnotation())
++    {
++      for (int col = 0; col < od.getWidth() && !resizeAgain; col++)
++      {
++        mg.translate(col, od.getSequencesHeight());
++        ap.annotationPanel.renderer.drawGraph(mg,
++                av.getAlignmentConservationAnnotation(),
++                av.getAlignmentConservationAnnotation().annotations,
++                (int) (sampleCol) + 1, od.getGraphHeight(),
++                (int) (col * sampleCol), (int) (col * sampleCol) + 1);
++        mg.translate(-col, -od.getSequencesHeight());
++      }
++    }
++    System.gc();
++
++    resizing = false;
++
++    setBoxPosition();
++
++    if (resizeAgain)
++    {
++      resizeAgain = false;
++      updateOverviewImage();
++    }
++  }
++
++  /*
++   * Build the overview panel image
++   */
++  private void buildImage(float sampleRow, float sampleCol, Graphics mg)
++  {
++    int lastcol = 0;
++    int lastrow = 0;
++    int xstart = 0;
++    int ystart = 0;
++    Color color = Color.yellow;
++    int sameRow = 0;
++    int sameCol = 0;
++
++    SequenceI seq = null;
++
++    final boolean hasHiddenCols = av.hasHiddenColumns();
++    boolean hiddenRow = false;
++
++    for (int row = 0; row <= od.getSequencesHeight() && !resizeAgain; row++)
++    {
++      if ((int) (row * sampleRow) == lastrow)
++      {
++        sameRow++;
++      }
++      else
++      {
++        // get the sequence which would be at alignment index 'lastrow' if no
++        // columns were hidden, and determine whether it is hidden or not
++        hiddenRow = av.getAlignment().isHidden(lastrow);
++        seq = av.getAlignment().getSequenceAtAbsoluteIndex(lastrow);
++
++        for (int col = 0; col < od.getWidth(); col++)
++        {
++          if ((int) (col * sampleCol) == lastcol
++                  && (int) (row * sampleRow) == lastrow)
++          {
++            sameCol++;
++          }
++          else
++          {
++            lastcol = (int) (col * sampleCol);
++
++            color = getColumnColourFromSequence(seq, hiddenRow,
++                    hasHiddenCols, lastcol);
++
++            mg.setColor(color);
++            if (sameCol == 1 && sameRow == 1)
++            {
++              mg.drawLine(xstart, ystart, xstart, ystart);
++            }
++            else
++            {
++              mg.fillRect(xstart, ystart, sameCol, sameRow);
++            }
++
++            xstart = col;
++            sameCol = 1;
++          }
++        }
++        lastrow = (int) (row * sampleRow);
++        ystart = row;
++        sameRow = 1;
++      }
++    }
++  }
++
++  /*
++   * Find the colour of a sequence at a specified column position
++   */
++  private Color getColumnColourFromSequence(
++          jalview.datamodel.SequenceI seq, boolean hiddenRow,
++          boolean hasHiddenCols, int lastcol)
++  {
++    Color color;
++    if (seq.getLength() > lastcol)
++    {
++      color = sr.getResidueBoxColour(seq, lastcol);
++
++      if (av.isShowSequenceFeatures())
++      {
++        color = fr.findFeatureColour(color, seq, lastcol);
++      }
++    }
++    else
++    {
++      color = Color.white;
++    }
++
++    if (hiddenRow
++            || (hasHiddenCols && !av.getColumnSelection()
++                    .isVisible(lastcol)))
++    {
++      color = color.darker().darker();
++    }
++    return color;
++  }
++
++  /**
++   * Update the overview panel box when the associated alignment panel is
++   * changed
++   * 
++   */
++  public void setBoxPosition()
++  {
++    od.setBoxPosition(av.getAlignment()
++            .getHiddenSequences(), av.getColumnSelection(), av.getRanges());
++    repaint();
++  }
++
++  @Override
++  public void update(Graphics g)
++  {
++    paint(g);
++  }
++
++  @Override
++  public void paint(Graphics g)
++  {
++    Graphics og = offscreen.getGraphics();
++    if (miniMe != null)
++    {
++      og.drawImage(miniMe, 0, 0, this);
++      og.setColor(Color.red);
++      od.drawBox(og);
++      g.drawImage(offscreen, 0, 0, this);
++    }
++  }
++
++}
@@@ -1922,38 -1950,10 +1951,43 @@@ public class Alignment implements Align
      }
      return new int[] { startPos, endPos };
    }
 -
+   @Override
+   public void setHiddenColumns(HiddenColumns cols)
+   {
+     hiddenCols = cols;
+   }
 +
 +  Map<Object, ContactMatrixI> contactmaps = new HashMap<Object, ContactMatrixI>();
 +  @Override
 +  public
 +  ContactListI getContactListFor(AlignmentAnnotation _aa, int column)
 +  {
 +    ContactMatrixI cm = contactmaps.get(_aa.annotationId);
 +    if (cm == null)
 +    {
 +      return null;
 +    }
 +    return cm.getContactList(column);
 +  }
 +
 +  @Override
 +  public AlignmentAnnotation addContactList(ContactMatrixI cm)
 +  {
 +    Annotation _aa[] = new Annotation[getWidth()];
 +    Annotation dummy = new Annotation(0.0f);
 +    for (int i = 0; i < _aa.length; _aa[i++] = dummy)
 +    {
 +      ;
 +    }
 +    AlignmentAnnotation aa = new AlignmentAnnotation("Contact Matrix",
 +            "Contact Matrix", _aa);
 +    aa.graph = AlignmentAnnotation.CUSTOMRENDERER;
 +    aa.graphMin = cm.getMin();
 +    aa.graphMax = cm.getMax();
 +    aa.editable = false;
 +    // aa.autoCalculated = true;
 +    contactmaps.put(aa.annotationId, cm);
 +    addAnnotation(aa);
 +    return aa;
 +  }
  }
@@@ -545,15 -589,6 +589,17 @@@ public interface AlignmentI extends Ann
     */
    public int[] getVisibleStartAndEndIndex(List<int[]> hiddenCols);
  
 -  public void setHiddenColumns(HiddenColumns cols);
 +  /**
 +   * resolve a contact list instance (if any) associated with the annotation row
 +   * and column position
 +   * 
 +   * @param _aa
 +   * @param column
 +   * @return
 +   */
 +  ContactListI getContactListFor(AlignmentAnnotation _aa, int column);
 +
 +  AlignmentAnnotation addContactList(ContactMatrixI cm);
++  public void setHiddenColumns(HiddenColumns cols);
  }
index 0000000,0000000..5464596
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,143 @@@
++package jalview.datamodel;
++
++
++/**
++ * A profile for one column of an alignment
++ * 
++ * @author gmcarstairs
++ *
++ */
++public class Profile implements ProfileI
++{
++  /*
++   * an object holding counts of symbols in the profile
++   */
++  private ResidueCount counts;
++
++  /*
++   * the number of sequences (gapped or not) in the profile
++   */
++  private int height;
++
++  /*
++   * the number of non-gapped sequences in the profile
++   */
++  private int gapped;
++
++  /*
++   * the highest count for any residue in the profile
++   */
++  private int maxCount;
++
++  /*
++   * the residue (e.g. K) or residues (e.g. KQW) with the
++   * highest count in the profile
++   */
++  private String modalResidue;
++
++  /**
++   * Constructor which allows derived data to be stored without having to store
++   * the full profile
++   * 
++   * @param seqCount
++   *          the number of sequences in the profile
++   * @param gaps
++   *          the number of gapped sequences
++   * @param max
++   *          the highest count for any residue
++   * @param modalres
++   *          the residue (or concatenated residues) with the highest count
++   */
++  public Profile(int seqCount, int gaps, int max, String modalRes)
++  {
++    this.height = seqCount;
++    this.gapped = gaps;
++    this.maxCount = max;
++    this.modalResidue = modalRes;
++  }
++
++  /* (non-Javadoc)
++   * @see jalview.datamodel.ProfileI#setCounts(jalview.datamodel.ResidueCount)
++   */
++  @Override
++  public void setCounts(ResidueCount residueCounts)
++  {
++    this.counts = residueCounts;
++  }
++
++  /* (non-Javadoc)
++   * @see jalview.datamodel.ProfileI#getPercentageIdentity(boolean)
++   */
++  @Override
++  public float getPercentageIdentity(boolean ignoreGaps)
++  {
++    if (height == 0)
++    {
++      return 0f;
++    }
++    float pid = 0f;
++    if (ignoreGaps && gapped < height)
++    {
++      pid = (maxCount * 100f) / (height - gapped);
++    }
++    else
++    {
++      pid = (maxCount * 100f) / height;
++    }
++    return pid;
++  }
++
++  /* (non-Javadoc)
++   * @see jalview.datamodel.ProfileI#getCounts()
++   */
++  @Override
++  public ResidueCount getCounts()
++  {
++    return counts;
++  }
++
++  /* (non-Javadoc)
++   * @see jalview.datamodel.ProfileI#getHeight()
++   */
++  @Override
++  public int getHeight()
++  {
++    return height;
++  }
++
++  /* (non-Javadoc)
++   * @see jalview.datamodel.ProfileI#getGapped()
++   */
++  @Override
++  public int getGapped()
++  {
++    return gapped;
++  }
++
++  /* (non-Javadoc)
++   * @see jalview.datamodel.ProfileI#getMaxCount()
++   */
++  @Override
++  public int getMaxCount()
++  {
++    return maxCount;
++  }
++
++  /* (non-Javadoc)
++   * @see jalview.datamodel.ProfileI#getModalResidue()
++   */
++  @Override
++  public String getModalResidue()
++  {
++    return modalResidue;
++  }
++
++  /* (non-Javadoc)
++   * @see jalview.datamodel.ProfileI#getNonGapped()
++   */
++  @Override
++  public int getNonGapped()
++  {
++    return height - gapped;
++  }
++}
index 0000000,0000000..cf2b394
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,67 @@@
++package jalview.datamodel;
++
++public interface ProfileI
++{
++
++  /**
++   * Set the full profile of counts
++   * 
++   * @param residueCounts
++   */
++  public abstract void setCounts(ResidueCount residueCounts);
++
++  /**
++   * Returns the percentage identity of the profile, i.e. the highest proportion
++   * of conserved (equal) symbols. The percentage is as a fraction of all
++   * sequences, or only ungapped sequences if flag ignoreGaps is set true.
++   * 
++   * @param ignoreGaps
++   * @return
++   */
++  public abstract float getPercentageIdentity(boolean ignoreGaps);
++
++  /**
++   * Returns the full symbol counts for this profile
++   * 
++   * @return
++   */
++  public abstract ResidueCount getCounts();
++
++  /**
++   * Returns the number of sequences in the profile
++   * 
++   * @return
++   */
++  public abstract int getHeight();
++
++  /**
++   * Returns the number of sequences in the profile which had a gap character
++   * (or were too short to be included in this column's profile)
++   * 
++   * @return
++   */
++  public abstract int getGapped();
++
++  /**
++   * Returns the highest count for any symbol(s) in the profile
++   * 
++   * @return
++   */
++  public abstract int getMaxCount();
++
++  /**
++   * Returns the symbol (or concatenated symbols) which have the highest count
++   * in the profile, or an empty string if there were no symbols counted
++   * 
++   * @return
++   */
++  public abstract String getModalResidue();
++
++  /**
++   * Answers the number of non-gapped sequences in the profile
++   * 
++   * @return
++   */
++  public abstract int getNonGapped();
++
++}
index 0000000,0000000..98a8c6d
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,43 @@@
++package jalview.datamodel;
++
++public class Profiles implements ProfilesI
++{
++
++  private ProfileI[] profiles;
++
++  public Profiles(ProfileI[] p)
++  {
++    profiles = p;
++  }
++
++  /**
++   * Returns the profile for the given column, or null if none found
++   * 
++   * @param col
++   */
++  @Override
++  public ProfileI get(int col)
++  {
++    return profiles != null && col >= 0 && col < profiles.length ? profiles[col]
++            : null;
++  }
++
++  /**
++   * Returns the first column (base 0) covered by the profiles
++   */
++  @Override
++  public int getStartColumn()
++  {
++    return 0;
++  }
++
++  /**
++   * Returns the last column (base 0) covered by the profiles
++   */
++  @Override
++  public int getEndColumn()
++  {
++    return profiles == null ? 0 : profiles.length - 1;
++  }
++
++}
index 0000000,0000000..6719de6
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,12 @@@
++package jalview.datamodel;
++
++public interface ProfilesI
++{
++
++  ProfileI get(int i);
++
++  int getStartColumn();
++
++  int getEndColumn();
++
++}
index 0000000,0000000..0d0348c
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,621 @@@
++package jalview.datamodel;
++
++import jalview.util.Comparison;
++import jalview.util.Format;
++import jalview.util.QuickSort;
++import jalview.util.SparseCount;
++
++/**
++ * A class to count occurrences of residues in a profile, optimised for speed
++ * and memory footprint.
++ * @author gmcarstairs
++ *
++ */
++public class ResidueCount
++{
++  /**
++   * A data bean to hold the results of counting symbols
++   */
++  public class SymbolCounts
++  {
++    /**
++     * the symbols seen (as char values), in no particular order
++     */
++    public final char[] symbols;
++
++    /**
++     * the counts for each symbol, in the same order as the symbols
++     */
++    public final int[] values;
++
++    SymbolCounts(char[] s, int[] v)
++    {
++      symbols = s;
++      values = v;
++    }
++  }
++
++  private static final int TOUPPERCASE = 'A' - 'a';
++
++  /*
++   * nucleotide symbols to count (including N unknown)
++   */
++  private static final String NUCS = "ACGNTU";
++
++  /*
++   * amino acid symbols to count (including X unknown)
++   * NB we also include U so as to support counting of RNA bases
++   * in the "don't know" case of nucleotide / peptide
++   */
++  private static final String AAS = "ACDEFGHIKLMNPQRSTUVWXY";
++
++  private static final int GAP_COUNT = 0;
++
++  /*
++   * fast lookup tables holding the index into our count
++   * arrays of each symbol; index 0 is reserved for gap counting
++   */
++  private static int[] NUC_INDEX = new int[26];
++
++  private static int[] AA_INDEX = new int[26];
++  static
++  {
++    for (int i = 0; i < NUCS.length(); i++)
++    {
++      NUC_INDEX[NUCS.charAt(i) - 'A'] = i + 1;
++    }
++    for (int i = 0; i < AAS.length(); i++)
++    {
++      AA_INDEX[AAS.charAt(i) - 'A'] = i + 1;
++    }
++  }
++
++  /*
++   * counts array, just big enough for the nucleotide or peptide
++   * character set (plus gap counts in position 0)
++   */
++  private short[] counts;
++
++  /*
++   * alternative array of int counts for use if any count 
++   * exceeds the maximum value of short (32767)
++   */
++  private int[] intCounts;
++
++  /*
++   * flag set if we switch from short to int counts
++   */
++  private boolean useIntCounts;
++
++  /*
++   * general-purpose counter, only for use for characters
++   * that are not in the expected alphabet
++   */
++  private SparseCount otherData;
++
++  /*
++   * keeps track of the maximum count value recorded
++   * (if this class ever allows decrements, would need to
++   * calculate this on request instead) 
++   */
++  int maxCount;
++
++  /*
++   * if we think we are counting nucleotide, can get by with smaller
++   * array to hold counts
++   */
++  private boolean isNucleotide;
++
++  /**
++   * Default constructor allocates arrays able to count either nucleotide or
++   * peptide bases. Use this constructor if not sure which the data is.
++   */
++  public ResidueCount()
++  {
++    this(false);
++  }
++
++  /**
++   * Constructor that allocates an array just big enough for the anticipated
++   * characters, plus one position to count gaps
++   */
++  public ResidueCount(boolean nucleotide)
++  {
++    isNucleotide = nucleotide;
++    int charsToCount = nucleotide ? NUCS.length() : AAS.length();
++    counts = new short[charsToCount + 1];
++  }
++
++  /**
++   * Increments the count for the given character. The supplied character may be
++   * upper or lower case but counts are for the upper case only. Gap characters
++   * (space, ., -) are all counted together.
++   * 
++   * @param c
++   * @return the new value of the count for the character
++   */
++  public int add(final char c)
++  {
++    char u = toUpperCase(c);
++    int newValue = 0;
++    int offset = getOffset(u);
++
++    /*
++     * offset 0 is reserved for gap counting, so 0 here means either
++     * an unexpected character, or a gap character passed in error
++     */
++    if (offset == 0)
++    {
++      if (Comparison.isGap(u))
++      {
++        newValue = addGap();
++      }
++      else
++      {
++        newValue = addOtherCharacter(u);
++      }
++    }
++    else
++    {
++      newValue = increment(offset);
++    }
++    return newValue;
++  }
++
++  /**
++   * Increment the count at the specified offset. If this would result in short
++   * overflow, promote to counting int values instead.
++   * 
++   * @param offset
++   * @return the new value of the count at this offset
++   */
++  int increment(int offset)
++  {
++    int newValue = 0;
++    if (useIntCounts)
++    {
++      newValue = intCounts[offset];
++      intCounts[offset] = ++newValue;
++    }
++    else
++    {
++      if (counts[offset] == Short.MAX_VALUE)
++      {
++        handleOverflow();
++        newValue = intCounts[offset];
++        intCounts[offset] = ++newValue;
++      }
++      else
++      {
++        newValue = counts[offset];
++        counts[offset] = (short) ++newValue;
++      }
++    }
++    maxCount = Math.max(maxCount, newValue);
++    return newValue;
++  }
++
++  /**
++   * Switch from counting in short to counting in int
++   */
++  synchronized void handleOverflow()
++  {
++    intCounts = new int[counts.length];
++    for (int i = 0; i < counts.length; i++)
++    {
++      intCounts[i] = counts[i];
++    }
++    counts = null;
++    useIntCounts = true;
++  }
++
++  /**
++   * Returns this character's offset in the count array
++   * 
++   * @param c
++   * @return
++   */
++  int getOffset(char c)
++  {
++    int offset = 0;
++    if ('A' <= c && c <= 'Z')
++    {
++      offset = isNucleotide ? NUC_INDEX[c - 'A'] : AA_INDEX[c - 'A'];
++    }
++    return offset;
++  }
++
++  /**
++   * @param c
++   * @return
++   */
++  protected char toUpperCase(final char c)
++  {
++    char u = c;
++    if ('a' <= c && c <= 'z')
++    {
++      u = (char) (c + TOUPPERCASE);
++    }
++    return u;
++  }
++
++  /**
++   * Increment count for some unanticipated character. The first time this
++   * called, a SparseCount is instantiated to hold these 'extra' counts.
++   * 
++   * @param c
++   * @return the new value of the count for the character
++   */
++  int addOtherCharacter(char c)
++  {
++    if (otherData == null)
++    {
++      otherData = new SparseCount();
++    }
++    int newValue = otherData.add(c, 1);
++    maxCount = Math.max(maxCount, newValue);
++    return newValue;
++  }
++
++  /**
++   * Set count for some unanticipated character. The first time this called, a
++   * SparseCount is instantiated to hold these 'extra' counts.
++   * 
++   * @param c
++   * @param value
++   */
++  void setOtherCharacter(char c, int value)
++  {
++    if (otherData == null)
++    {
++      otherData = new SparseCount();
++    }
++    otherData.put(c, value);
++  }
++
++  /**
++   * Increment count of gap characters
++   * 
++   * @return the new count of gaps
++   */
++  public int addGap()
++  {
++    int newValue;
++    if (useIntCounts)
++    {
++      newValue = ++intCounts[GAP_COUNT];
++    }
++    else
++    {
++      newValue = ++counts[GAP_COUNT];
++    }
++    return newValue;
++  }
++
++  /**
++   * Answers true if we are counting ints (only after overflow of short counts)
++   * 
++   * @return
++   */
++  boolean isCountingInts()
++  {
++    return useIntCounts;
++  }
++
++  /**
++   * Sets the count for the given character. The supplied character may be upper
++   * or lower case but counts are for the upper case only.
++   * 
++   * @param c
++   * @param count
++   */
++  public void put(char c, int count)
++  {
++    char u = toUpperCase(c);
++    int offset = getOffset(u);
++
++    /*
++     * offset 0 is reserved for gap counting, so 0 here means either
++     * an unexpected character, or a gap character passed in error
++     */
++    if (offset == 0)
++    {
++      if (Comparison.isGap(u))
++      {
++        set(0, count);
++      }
++      else
++      {
++        setOtherCharacter(u, count);
++        maxCount = Math.max(maxCount, count);
++      }
++    }
++    else
++    {
++      set(offset, count);
++      maxCount = Math.max(maxCount, count);
++    }
++  }
++
++  /**
++   * Sets the count at the specified offset. If this would result in short
++   * overflow, promote to counting int values instead.
++   * 
++   * @param offset
++   * @param value
++   */
++  void set(int offset, int value)
++  {
++    if (useIntCounts)
++    {
++      intCounts[offset] = value;
++    }
++    else
++    {
++      if (value > Short.MAX_VALUE || value < Short.MIN_VALUE)
++      {
++        handleOverflow();
++        intCounts[offset] = value;
++      }
++      else
++      {
++        counts[offset] = (short) value;
++      }
++    }
++  }
++
++  /**
++   * Returns the count for the given character, or zero if no count held
++   * 
++   * @param c
++   * @return
++   */
++  public int getCount(char c)
++  {
++    char u = toUpperCase(c);
++    int offset = getOffset(u);
++    if (offset == 0)
++    {
++      if (!Comparison.isGap(u))
++      {
++        // should have called getGapCount()
++        return otherData == null ? 0 : otherData.get(u);
++      }
++    }
++    return useIntCounts ? intCounts[offset] : counts[offset];
++  }
++
++  public int getGapCount()
++  {
++    return useIntCounts ? intCounts[0] : counts[0];
++  }
++
++  /**
++   * Answers true if this object wraps a counter for unexpected characters
++   * 
++   * @return
++   */
++  boolean isUsingOtherData()
++  {
++    return otherData != null;
++  }
++
++  /**
++   * Returns the character (or concatenated characters) for the symbol(s) with
++   * the given count in the profile. Can be used to get the modal residue by
++   * supplying the modal count value. Returns an empty string if no symbol has
++   * the given count. The symbols are in alphabetic order of standard peptide or
++   * nucleotide characters, followed by 'other' symbols if any.
++   * 
++   * @return
++   */
++  public String getResiduesForCount(int count)
++  {
++    if (count == 0)
++    {
++      return "";
++    }
++
++    /*
++     * find counts for the given value and append the
++     * corresponding symbol
++     */
++    StringBuilder modal = new StringBuilder();
++    if (useIntCounts)
++    {
++      for (int i = 1; i < intCounts.length; i++)
++      {
++        if (intCounts[i] == count)
++        {
++          modal.append(isNucleotide ? NUCS.charAt(i - 1) : AAS
++                  .charAt(i - 1));
++        }
++      }
++    }
++    else
++    {
++      for (int i = 1; i < counts.length; i++)
++      {
++        if (counts[i] == count)
++        {
++          modal.append(isNucleotide ? NUCS.charAt(i - 1) : AAS
++                  .charAt(i - 1));
++        }
++      }
++    }
++    if (otherData != null)
++    {
++      for (int i = 0; i < otherData.size(); i++)
++      {
++        if (otherData.valueAt(i) == count)
++        {
++          modal.append((char) otherData.keyAt(i));
++        }
++      }
++    }
++    return modal.toString();
++  }
++
++  /**
++   * Returns the highest count for any symbol(s) in the profile (excluding gap)
++   * 
++   * @return
++   */
++  public int getModalCount()
++  {
++    return maxCount;
++  }
++
++  /**
++   * Returns the number of distinct symbols with a non-zero count (excluding the
++   * gap symbol)
++   * 
++   * @return
++   */
++  public int size() {
++    int size = 0;
++    if (useIntCounts)
++    {
++      for (int i = 1; i < intCounts.length; i++)
++      {
++        if (intCounts[i] > 0)
++        {
++          size++;
++        }
++      }
++    }
++    else
++    {
++      for (int i = 1; i < counts.length; i++)
++      {
++        if (counts[i] > 0)
++        {
++          size++;
++        }
++      }
++    }
++
++    /*
++     * include 'other' characters recorded (even if count is zero
++     * though that would be a strange use case)
++     */
++    if (otherData != null)
++    {
++      size += otherData.size();
++    }
++
++    return size;
++  }
++
++  /**
++   * Returns a data bean holding those symbols that have a non-zero count
++   * (excluding the gap symbol), with their counts.
++   * 
++   * @return
++   */
++  public SymbolCounts getSymbolCounts()
++  {
++    int size = size();
++    char[] symbols = new char[size];
++    int[] values = new int[size];
++    int j = 0;
++
++    if (useIntCounts)
++    {
++      for (int i = 1; i < intCounts.length; i++)
++      {
++        if (intCounts[i] > 0)
++        {
++          char symbol = isNucleotide ? NUCS.charAt(i - 1) : AAS
++                  .charAt(i - 1);
++          symbols[j] = symbol;
++          values[j] = intCounts[i];
++          j++;
++        }
++      }
++    }
++    else
++    {
++      for (int i = 1; i < counts.length; i++)
++      {
++        if (counts[i] > 0)
++        {
++          char symbol = isNucleotide ? NUCS.charAt(i - 1) : AAS
++                  .charAt(i - 1);
++          symbols[j] = symbol;
++          values[j] = counts[i];
++          j++;
++        }
++      }
++    }
++    if (otherData != null)
++    {
++      for (int i = 0; i < otherData.size(); i++)
++      {
++        symbols[j] = (char) otherData.keyAt(i);
++        values[j] = otherData.valueAt(i);
++        j++;
++      }
++    }
++
++    return new SymbolCounts(symbols, values);
++  }
++
++  /**
++   * Returns a tooltip string showing residues in descending order of their
++   * percentage frequency in the profile
++   * 
++   * @param normaliseBy
++   *          the divisor for residue counts (may or may not include gapped
++   *          sequence count)
++   * @param percentageDecPl
++   *          the number of decimal places to show in percentages
++   * @return
++   */
++  public String getTooltip(int normaliseBy, int percentageDecPl)
++  {
++    SymbolCounts symbolCounts = getSymbolCounts();
++    char[] ca = symbolCounts.symbols;
++    int[] vl = symbolCounts.values;
++
++    /*
++     * sort characters into ascending order of their counts
++     */
++    QuickSort.sort(vl, ca);
++
++    /*
++     * traverse in reverse order (highest count first) to build tooltip
++     */
++    boolean first = true;
++    StringBuilder sb = new StringBuilder(64);
++    for (int c = ca.length - 1; c >= 0; c--)
++    {
++      final char residue = ca[c];
++      // TODO combine residues which share a percentage
++      // (see AAFrequency.completeCdnaConsensus)
++      float tval = (vl[c] * 100f) / normaliseBy;
++      sb.append(first ? "" : "; ").append(residue).append(" ");
++      Format.appendPercentage(sb, tval, percentageDecPl);
++      sb.append("%");
++      first = false;
++    }
++    return sb.toString();
++  }
++
++  /**
++   * Returns a string representation of the symbol counts, for debug purposes.
++   */
++  @Override
++  public String toString()
++  {
++    StringBuilder sb = new StringBuilder();
++    sb.append("[ ");
++    SymbolCounts sc = getSymbolCounts();
++    for (int i = 0; i < sc.symbols.length; i++)
++    {
++      sb.append(sc.symbols[i]).append(":").append(sc.values[i]).append(" ");
++    }
++    sb.append("]");
++    return sb.toString();
++  }
++}
index 0000000,0000000..732f1dc
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,30 @@@
++package jalview.datamodel;
++
++/**
++ * An interface that describes one matched region of an alignment, as one
++ * contiguous portion of a single dataset sequence
++ */
++public interface SearchResultMatchI
++{
++  /**
++   * Returns the matched sequence
++   * 
++   * @return
++   */
++  SequenceI getSequence();
++
++  /**
++   * Returns the start position of the match in the sequence (base 1)
++   * 
++   * @return
++   */
++  int getStart();
++
++  /**
++   * Returns the end position of the match in the sequence (base 1)
++   * 
++   * @return
++   */
++  int getEnd();
++
++}
index 0000000,0000000..93183f2
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,84 @@@
++package jalview.datamodel;
++
++import java.util.BitSet;
++import java.util.List;
++
++/**
++ * An interface describing the result of a search or other operation which
++ * highlights matched regions of an alignment
++ */
++public interface SearchResultsI
++{
++
++  /**
++   * Adds one region to the results
++   * 
++   * @param seq
++   *          Sequence
++   * @param start
++   *          int
++   * @param end
++   *          int
++   * @return
++   */
++  SearchResultMatchI addResult(SequenceI seq, int start, int end);
++
++  /**
++   * Answers true if the search results include the given sequence (or its
++   * dataset sequence), else false
++   * 
++   * @param sequence
++   * @return
++   */
++  boolean involvesSequence(SequenceI sequence);
++
++  /**
++   * Returns an array of [from, to, from, to..] matched columns (base 0) between
++   * the given start and end columns of the given sequence. Returns null if no
++   * matches overlap the specified region.
++   * <p>
++   * Implementations should provide an optimised method to return locations to
++   * highlight on a visible portion of an alignment.
++   * 
++   * @param sequence
++   * @param start
++   *          first column of range (base 0, inclusive)
++   * @param end
++   *          last column of range base 0, inclusive)
++   * @return int[]
++   */
++  int[] getResults(SequenceI sequence, int start, int end);
++
++  /**
++   * Returns the number of matches found
++   * 
++   * @return
++   */
++  int getSize();
++
++  /**
++   * Returns true if no search result matches are held.
++   * 
++   * @return
++   */
++  boolean isEmpty();
++
++  /**
++   * Returns the list of matches.
++   * 
++   * @return
++   */
++  List<SearchResultMatchI> getResults();
++
++  /**
++   * Set bits in a bitfield for all columns in the given sequence collection
++   * that are highlighted
++   * 
++   * @param sqcol
++   *          the set of sequences to search for highlighted regions
++   * @param bs
++   *          bitset to set
++   * @return number of bits set
++   */
++  int markColumns(SequenceCollectionI sqcol, BitSet bs);
++}
index 0000000,0000000..9b32846
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,670 @@@
++/*
++ * 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.ext.jmol;
++
++import jalview.datamodel.AlignmentAnnotation;
++import jalview.datamodel.Annotation;
++import jalview.datamodel.PDBEntry;
++import jalview.datamodel.SequenceI;
++import jalview.io.FileParse;
++import jalview.io.StructureFile;
++import jalview.schemes.ResidueProperties;
++import jalview.structure.StructureImportSettings;
++import jalview.util.Format;
++import jalview.util.MessageManager;
++
++import java.io.IOException;
++import java.util.ArrayList;
++import java.util.HashMap;
++import java.util.List;
++import java.util.Map;
++import java.util.Vector;
++
++import javajs.awt.Dimension;
++
++import org.jmol.api.JmolStatusListener;
++import org.jmol.api.JmolViewer;
++import org.jmol.c.CBK;
++import org.jmol.c.STR;
++import org.jmol.modelset.ModelSet;
++import org.jmol.viewer.Viewer;
++
++import MCview.Atom;
++import MCview.PDBChain;
++import MCview.Residue;
++
++/**
++ * Import and process files with Jmol for file like PDB, mmCIF
++ * 
++ * @author jprocter
++ * 
++ */
++public class JmolParser extends StructureFile implements JmolStatusListener
++{
++  Viewer viewer = null;
++
++  public JmolParser(String inFile, String type) throws IOException
++  {
++    super(inFile, type);
++  }
++
++  public JmolParser(FileParse fp) throws IOException
++  {
++    super(fp);
++  }
++
++  public JmolParser()
++  {
++  }
++
++  /**
++   * Calls the Jmol library to parse the PDB/mmCIF file, and then inspects the
++   * resulting object model to generate Jalview-style sequences, with secondary
++   * structure annotation added where available (i.e. where it has been computed
++   * by Jmol using DSSP).
++   * 
++   * @see jalview.io.AlignFile#parse()
++   */
++  @Override
++  public void parse() throws IOException
++  {
++    setChains(new Vector<PDBChain>());
++    Viewer jmolModel = getJmolData();
++    jmolModel.openReader(getDataName(), getDataName(), getReader());
++    waitForScript(jmolModel);
++
++    /*
++     * Convert one or more Jmol Model objects to Jalview sequences
++     */
++    if (jmolModel.ms.mc > 0)
++    {
++      // ideally we do this
++      // try
++      // {
++      // setStructureFileType(jmolModel.evalString("show _fileType"));
++      // } catch (Exception q)
++      // {
++      // }
++      // ;
++      // instead, we distinguish .cif from non-.cif by filename
++      setStructureFileType(getDataName().toLowerCase().endsWith(".cif") ? PDBEntry.Type.MMCIF
++              .toString() : "PDB");
++
++      transformJmolModelToJalview(jmolModel.ms);
++    }
++  }
++
++  /**
++   * create a headless jmol instance for dataprocessing
++   * 
++   * @return
++   */
++  private Viewer getJmolData()
++  {
++    if (viewer == null)
++    {
++      try
++      {
++        /*
++         * params -o (output to sysout) -n (nodisplay) -x (exit when finished)
++         * see http://wiki.jmol.org/index.php/Jmol_Application
++         */
++        viewer = (Viewer) JmolViewer.allocateViewer(null, null, null, null,
++                null, "-x -o -n", this);
++        // ensure the 'new' (DSSP) not 'old' (Ramachandran) SS method is used
++        viewer.setBooleanProperty("defaultStructureDSSP", true);
++      } catch (ClassCastException x)
++      {
++        throw new Error(MessageManager.formatMessage(
++                "error.jmol_version_not_compatible_with_jalview_version",
++                new String[] { JmolViewer.getJmolVersion() }), x);
++      }
++    }
++    return viewer;
++  }
++
++  public void transformJmolModelToJalview(ModelSet ms) throws IOException
++  {
++    try
++    {
++      String lastID = "";
++      List<SequenceI> rna = new ArrayList<SequenceI>();
++      List<SequenceI> prot = new ArrayList<SequenceI>();
++      PDBChain tmpchain;
++      String pdbId = (String) ms.getInfo(0, "title");
++
++      if (pdbId == null)
++      {
++        setId(safeName(getDataName()));
++        setPDBIdAvailable(false);
++      }
++      else
++      {
++        setId(pdbId);
++        setPDBIdAvailable(true);
++      }
++      List<Atom> significantAtoms = convertSignificantAtoms(ms);
++      for (Atom tmpatom : significantAtoms)
++      {
++        try
++        {
++          tmpchain = findChain(tmpatom.chain);
++          if (tmpatom.resNumIns.trim().equals(lastID))
++          {
++            // phosphorylated protein - seen both CA and P..
++            continue;
++          }
++          tmpchain.atoms.addElement(tmpatom);
++        } catch (Exception e)
++        {
++          tmpchain = new PDBChain(getId(), tmpatom.chain);
++          getChains().add(tmpchain);
++          tmpchain.atoms.addElement(tmpatom);
++        }
++        lastID = tmpatom.resNumIns.trim();
++      }
++      xferSettings();
++
++      makeResidueList();
++      makeCaBondList();
++
++<<<<<<< HEAD
++=======
++      if (getId() == null)
++      {
++        // always use resource name, not the hardwired file
++        // Does the value of ID get used ? Behaviour needs to be
++        // documented and tested
++        setId(getDataName());
++      }
++>>>>>>> spike/JAL-2040_JAL-2137_phyre2
++      for (PDBChain chain : getChains())
++      {
++        SequenceI chainseq = postProcessChain(chain);
++        if (isRNA(chainseq))
++        {
++          rna.add(chainseq);
++        }
++        else
++        {
++          prot.add(chainseq);
++        }
++
++        if (StructureImportSettings.isProcessSecondaryStructure())
++        {
++          createAnnotation(chainseq, chain, ms.at);
++        }
++      }
++    } catch (OutOfMemoryError er)
++    {
++      System.out
++              .println("OUT OF MEMORY LOADING TRANSFORMING JMOL MODEL TO JALVIEW MODEL");
++      throw new IOException(
++              MessageManager
++                      .getString("exception.outofmemory_loading_mmcif_file"));
++    }
++  }
++
++  private List<Atom> convertSignificantAtoms(ModelSet ms)
++  {
++    List<Atom> significantAtoms = new ArrayList<Atom>();
++    HashMap<String, org.jmol.modelset.Atom> chainTerMap = new HashMap<String, org.jmol.modelset.Atom>();
++    org.jmol.modelset.Atom prevAtom = null;
++    for (org.jmol.modelset.Atom atom : ms.at)
++    {
++      if (atom.getAtomName().equalsIgnoreCase("CA")
++              || atom.getAtomName().equalsIgnoreCase("P"))
++      {
++        if (!atomValidated(atom, prevAtom, chainTerMap))
++        {
++          continue;
++        }
++        Atom curAtom = new Atom(atom.x, atom.y, atom.z);
++        curAtom.atomIndex = atom.getIndex();
++        curAtom.chain = atom.getChainIDStr();
++        curAtom.insCode = atom.group.getInsertionCode() == '\000' ? ' '
++                : atom.group.getInsertionCode();
++        curAtom.name = atom.getAtomName();
++        curAtom.number = atom.getAtomNumber();
++        curAtom.resName = atom.getGroup3(true);
++        curAtom.resNumber = atom.getResno();
++        curAtom.occupancy = ms.occupancies != null ? ms.occupancies[atom
++                .getIndex()] : Float.valueOf(atom.getOccupancy100());
++        String fmt = new Format("%4i").form(curAtom.resNumber);
++        curAtom.resNumIns = (fmt + curAtom.insCode);
++        curAtom.tfactor = atom.getBfactor100() / 100f;
++        curAtom.type = 0;
++        // significantAtoms.add(curAtom);
++        // ignore atoms from subsequent models
++        if (!significantAtoms.contains(curAtom))
++        {
++          significantAtoms.add(curAtom);
++        }
++        prevAtom = atom;
++      }
++    }
++    return significantAtoms;
++  }
++
++  private boolean atomValidated(org.jmol.modelset.Atom curAtom,
++          org.jmol.modelset.Atom prevAtom,
++          HashMap<String, org.jmol.modelset.Atom> chainTerMap)
++  {
++    // System.out.println("Atom: " + curAtom.getAtomNumber()
++    // + "   Last atom index " + curAtom.group.lastAtomIndex);
++    if (chainTerMap == null || prevAtom == null)
++    {
++      return true;
++    }
++    String curAtomChId = curAtom.getChainIDStr();
++    String prevAtomChId = prevAtom.getChainIDStr();
++    // new chain encoutered
++    if (!prevAtomChId.equals(curAtomChId))
++    {
++      // On chain switch add previous chain termination to xTerMap if not exists
++      if (!chainTerMap.containsKey(prevAtomChId))
++      {
++        chainTerMap.put(prevAtomChId, prevAtom);
++      }
++      // if current atom belongs to an already terminated chain and the resNum
++      // diff < 5 then mark as valid and update termination Atom
++      if (chainTerMap.containsKey(curAtomChId))
++      {
++        if (curAtom.getResno() < chainTerMap.get(curAtomChId).getResno())
++        {
++          return false;
++        }
++        if ((curAtom.getResno() - chainTerMap.get(curAtomChId).getResno()) < 5)
++        {
++          chainTerMap.put(curAtomChId, curAtom);
++          return true;
++        }
++        return false;
++      }
++    }
++    // atom with previously terminated chain encountered
++    else if (chainTerMap.containsKey(curAtomChId))
++    {
++      if (curAtom.getResno() < chainTerMap.get(curAtomChId).getResno())
++      {
++        return false;
++      }
++      if ((curAtom.getResno() - chainTerMap.get(curAtomChId).getResno()) < 5)
++      {
++        chainTerMap.put(curAtomChId, curAtom);
++        return true;
++      }
++      return false;
++    }
++    // HETATM with resNum jump > 2
++    return !(curAtom.isHetero() && ((curAtom.getResno() - prevAtom
++            .getResno()) > 2));
++  }
++
++  private void createAnnotation(SequenceI sequence, PDBChain chain,
++          org.jmol.modelset.Atom[] jmolAtoms)
++  {
++    char[] secstr = new char[sequence.getLength()];
++    char[] secstrcode = new char[sequence.getLength()];
++
++    // Ensure Residue size equals Seq size
++    if (chain.residues.size() != sequence.getLength())
++    {
++      return;
++    }
++    int annotIndex = 0;
++    for (Residue residue : chain.residues)
++    {
++      Atom repAtom = residue.getAtoms().get(0);
++      STR proteinStructureSubType = jmolAtoms[repAtom.atomIndex].group
++              .getProteinStructureSubType();
++      setSecondaryStructure(proteinStructureSubType, annotIndex, secstr,
++              secstrcode);
++      ++annotIndex;
++    }
++    addSecondaryStructureAnnotation(chain.pdbid, sequence, secstr,
++            secstrcode, chain.id, sequence.getStart());
++  }
++
++  /**
++   * Helper method that adds an AlignmentAnnotation for secondary structure to
++   * the sequence, provided at least one secondary structure prediction has been
++   * made
++   * 
++   * @param modelTitle
++   * @param seq
++   * @param secstr
++   * @param secstrcode
++   * @param chainId
++   * @param firstResNum
++   * @return
++   */
++  protected void addSecondaryStructureAnnotation(String modelTitle,
++          SequenceI sq, char[] secstr, char[] secstrcode, String chainId,
++          int firstResNum)
++  {
++    char[] seq = sq.getSequence();
++    boolean ssFound = false;
++    Annotation asecstr[] = new Annotation[seq.length + firstResNum - 1];
++    for (int p = 0; p < seq.length; p++)
++    {
++      if (secstr[p] >= 'A' && secstr[p] <= 'z')
++      {
++        try
++        {
++          asecstr[p] = new Annotation(String.valueOf(secstr[p]), null,
++                  secstrcode[p], Float.NaN);
++          ssFound = true;
++        } catch (Exception e)
++        {
++          // e.printStackTrace();
++        }
++      }
++    }
++
++    if (ssFound)
++    {
++      String mt = modelTitle == null ? getDataName() : modelTitle;
++      mt += chainId;
++      AlignmentAnnotation ann = new AlignmentAnnotation(
++              "Secondary Structure", "Secondary Structure for " + mt,
++              asecstr);
++      ann.belowAlignment = true;
++      ann.visible = true;
++      ann.autoCalculated = false;
++      ann.setCalcId(getClass().getName());
++      ann.adjustForAlignment();
++      ann.validateRangeAndDisplay();
++      annotations.add(ann);
++      sq.addAlignmentAnnotation(ann);
++    }
++  }
++
++  private void waitForScript(Viewer jmd)
++  {
++    while (jmd.isScriptExecuting())
++    {
++      try
++      {
++        Thread.sleep(50);
++
++      } catch (InterruptedException x)
++      {
++      }
++    }
++  }
++
++  /**
++   * Convert Jmol's secondary structure code to Jalview's, and stored it in the
++   * secondary structure arrays at the given sequence position
++   * 
++   * @param proteinStructureSubType
++   * @param pos
++   * @param secstr
++   * @param secstrcode
++   */
++  protected void setSecondaryStructure(STR proteinStructureSubType,
++          int pos, char[] secstr, char[] secstrcode)
++  {
++    switch (proteinStructureSubType)
++    {
++    case HELIX310:
++      secstr[pos] = '3';
++      break;
++    case HELIX:
++    case HELIXALPHA:
++      secstr[pos] = 'H';
++      break;
++    case HELIXPI:
++      secstr[pos] = 'P';
++      break;
++    case SHEET:
++      secstr[pos] = 'E';
++      break;
++    default:
++      secstr[pos] = 0;
++    }
++
++    switch (proteinStructureSubType)
++    {
++    case HELIX310:
++    case HELIXALPHA:
++    case HELIXPI:
++    case HELIX:
++      secstrcode[pos] = 'H';
++      break;
++    case SHEET:
++      secstrcode[pos] = 'E';
++      break;
++    default:
++      secstrcode[pos] = 0;
++    }
++  }
++
++  /**
++   * Convert any non-standard peptide codes to their standard code table
++   * equivalent. (Initial version only does Selenomethionine MSE->MET.)
++   * 
++   * @param threeLetterCode
++   * @param seq
++   * @param pos
++   */
++  protected void replaceNonCanonicalResidue(String threeLetterCode,
++          char[] seq, int pos)
++  {
++    String canonical = ResidueProperties
++            .getCanonicalAminoAcid(threeLetterCode);
++    if (canonical != null && !canonical.equalsIgnoreCase(threeLetterCode))
++    {
++      seq[pos] = ResidueProperties.getSingleCharacterCode(canonical);
++    }
++  }
++
++  /**
++   * Not implemented - returns null
++   */
++  @Override
++  public String print()
++  {
++    return null;
++  }
++
++  /**
++   * Not implemented
++   */
++  @Override
++  public void setCallbackFunction(String callbackType,
++          String callbackFunction)
++  {
++  }
++
++  @Override
++  public void notifyCallback(CBK cbType, Object[] data)
++  {
++    String strInfo = (data == null || data[1] == null ? null : data[1]
++            .toString());
++    switch (cbType)
++    {
++    case ECHO:
++      sendConsoleEcho(strInfo);
++      break;
++    case SCRIPT:
++      notifyScriptTermination((String) data[2],
++              ((Integer) data[3]).intValue());
++      break;
++    case MEASURE:
++      String mystatus = (String) data[3];
++      if (mystatus.indexOf("Picked") >= 0
++              || mystatus.indexOf("Sequence") >= 0)
++      {
++        // Picking mode
++        sendConsoleMessage(strInfo);
++      }
++      else if (mystatus.indexOf("Completed") >= 0)
++      {
++        sendConsoleEcho(strInfo.substring(strInfo.lastIndexOf(",") + 2,
++                strInfo.length() - 1));
++      }
++      break;
++    case MESSAGE:
++      sendConsoleMessage(data == null ? null : strInfo);
++      break;
++    case PICK:
++      sendConsoleMessage(strInfo);
++      break;
++    default:
++      break;
++    }
++  }
++
++  String lastConsoleEcho = "";
++
++  private void sendConsoleEcho(String string)
++  {
++    lastConsoleEcho += string;
++    lastConsoleEcho += "\n";
++  }
++
++  String lastConsoleMessage = "";
++
++  private void sendConsoleMessage(String string)
++  {
++    lastConsoleMessage += string;
++    lastConsoleMessage += "\n";
++  }
++
++  int lastScriptTermination = -1;
++
++  String lastScriptMessage = "";
++
++  private void notifyScriptTermination(String string, int intValue)
++  {
++    lastScriptMessage += string;
++    lastScriptMessage += "\n";
++    lastScriptTermination = intValue;
++  }
++
++  @Override
++  public boolean notifyEnabled(CBK callbackPick)
++  {
++    switch (callbackPick)
++    {
++    case MESSAGE:
++    case SCRIPT:
++    case ECHO:
++    case LOADSTRUCT:
++    case ERROR:
++      return true;
++    default:
++      return false;
++    }
++  }
++
++  /**
++   * Not implemented - returns null
++   */
++  @Override
++  public String eval(String strEval)
++  {
++    return null;
++  }
++
++  /**
++   * Not implemented - returns null
++   */
++  @Override
++  public float[][] functionXY(String functionName, int x, int y)
++  {
++    return null;
++  }
++
++  /**
++   * Not implemented - returns null
++   */
++  @Override
++  public float[][][] functionXYZ(String functionName, int nx, int ny, int nz)
++  {
++    return null;
++  }
++
++  /**
++   * Not implemented - returns null
++   */
++  @Override
++  public String createImage(String fileName, String imageType,
++          Object text_or_bytes, int quality)
++  {
++    return null;
++  }
++
++  /**
++   * Not implemented - returns null
++   */
++  @Override
++  public Map<String, Object> getRegistryInfo()
++  {
++    return null;
++  }
++
++  /**
++   * Not implemented
++   */
++  @Override
++  public void showUrl(String url)
++  {
++  }
++
++  /**
++   * Not implemented - returns null
++   */
++  @Override
++  public Dimension resizeInnerPanel(String data)
++  {
++    return null;
++  }
++
++  @Override
++  public Map<String, Object> getJSpecViewProperty(String arg0)
++  {
++    return null;
++  }
++
++  public boolean isPredictSecondaryStructure()
++  {
++    return predictSecondaryStructure;
++  }
++
++  public void setPredictSecondaryStructure(boolean predictSecondaryStructure)
++  {
++    this.predictSecondaryStructure = predictSecondaryStructure;
++  }
++
++  public boolean isVisibleChainAnnotation()
++  {
++    return visibleChainAnnotation;
++  }
++
++  public void setVisibleChainAnnotation(boolean visibleChainAnnotation)
++  {
++    this.visibleChainAnnotation = visibleChainAnnotation;
++  }
++
++}
index 0000000,0000000..ce3abb5
new file mode 100755 (executable)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,468 @@@
++/*
++ * 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.gui;
++
++import jalview.datamodel.SequenceI;
++import jalview.renderer.AnnotationRenderer;
++import jalview.viewmodel.OverviewDimensions;
++import jalview.renderer.seqfeatures.FeatureColourFinder;
++
++import java.awt.Color;
++import java.awt.Dimension;
++import java.awt.Graphics;
++import java.awt.event.ComponentAdapter;
++import java.awt.event.ComponentEvent;
++import java.awt.event.MouseAdapter;
++import java.awt.event.MouseEvent;
++import java.awt.event.MouseMotionAdapter;
++import java.awt.image.BufferedImage;
++
++import javax.swing.JPanel;
++
++/**
++ * Panel displaying an overview of the full alignment, with an interactive box
++ * representing the viewport onto the alignment.
++ * 
++ * @author $author$
++ * @version $Revision$
++ */
++public class OverviewPanel extends JPanel implements Runnable
++{
++  private static final Color TRANS_GREY = new Color(100, 100, 100, 25);
++
++  private final AnnotationRenderer renderer = new AnnotationRenderer();
++
++  private OverviewDimensions od;
++
++  private BufferedImage miniMe;
++
++  private BufferedImage lastMiniMe = null;
++
++  private AlignViewport av;
++
++  private AlignmentPanel ap;
++
++  //
++  private boolean resizing = false;
++
++  // This is set true if the user resizes whilst
++  // the overview is being calculated
++  private boolean resizeAgain = false;
++
++  // Can set different properties in this seqCanvas than
++  // main visible SeqCanvas
++  private SequenceRenderer sr;
++
++  private jalview.renderer.seqfeatures.FeatureRenderer fr;
++
++  /**
++   * Creates a new OverviewPanel object.
++   * 
++   * @param alPanel
++   *          The alignment panel which is shown in the overview panel
++   */
++  public OverviewPanel(AlignmentPanel alPanel)
++  {
++    this.av = alPanel.av;
++    this.ap = alPanel;
++    setLayout(null);
++
++    sr = new SequenceRenderer(av);
++    sr.renderGaps = false;
++    sr.forOverview = true;
++    fr = new FeatureRenderer(alPanel);
++
++    od = new OverviewDimensions(av.getRanges(), av.isShowAnnotation());
++
++    addComponentListener(new ComponentAdapter()
++    {
++      @Override
++      public void componentResized(ComponentEvent evt)
++      {
++        if ((getWidth() != od.getWidth())
++                || (getHeight() != (od.getHeight())))
++        {
++          updateOverviewImage();
++        }
++      }
++    });
++
++    addMouseMotionListener(new MouseMotionAdapter()
++    {
++      @Override
++      public void mouseDragged(MouseEvent evt)
++      {
++        if (!av.getWrapAlignment())
++        {
++          od.updateViewportFromMouse(evt.getX(), evt.getY(), av
++                  .getAlignment().getHiddenSequences(), av
++                  .getColumnSelection(), av.getRanges());
++          ap.setScrollValues(od.getScrollCol(), od.getScrollRow());
++        }
++      }
++    });
++
++    addMouseListener(new MouseAdapter()
++    {
++      @Override
++      public void mousePressed(MouseEvent evt)
++      {
++        if (!av.getWrapAlignment())
++        {
++          od.updateViewportFromMouse(evt.getX(), evt.getY(), av
++                  .getAlignment().getHiddenSequences(), av
++                  .getColumnSelection(), av.getRanges());
++          ap.setScrollValues(od.getScrollCol(), od.getScrollRow());
++        }
++      }
++    });
++
++    updateOverviewImage();
++  }
++
++  /**
++   * Updates the overview image when the related alignment panel is updated
++   */
++  public void updateOverviewImage()
++  {
++    if (resizing)
++    {
++      resizeAgain = true;
++      return;
++    }
++
++    resizing = true;
++
++    if ((getWidth() > 0) && (getHeight() > 0))
++    {
++      od.setWidth(getWidth());
++      od.setHeight(getHeight());
++    }
++
++    setPreferredSize(new Dimension(od.getWidth(), od.getHeight()));
++
++    Thread thread = new Thread(this);
++    thread.start();
++    repaint();
++  }
++
++  @Override
++  public void run()
++  {
++    miniMe = null;
++
++    if (av.isShowSequenceFeatures())
++    {
++      fr.transferSettings(ap.getSeqPanel().seqCanvas.getFeatureRenderer());
++    }
++
++    // why do we need to set preferred size again? was set in
++    // updateOverviewImage
++    setPreferredSize(new Dimension(od.getWidth(), od.getHeight()));
++
++    miniMe = new BufferedImage(od.getWidth(), od.getHeight(),
++            BufferedImage.TYPE_INT_RGB);
++
++    Graphics mg = miniMe.getGraphics();
++    mg.setColor(Color.orange);
++    mg.fillRect(0, 0, od.getWidth(), miniMe.getHeight());
++
++<<<<<<< HEAD
++    // calculate sampleCol and sampleRow
++    // alignment width is max number of residues/bases
++    // alignment height is number of sequences
++    int alwidth = av.getAlignment().getWidth();
++    int alheight = av.getAlignment().getAbsoluteHeight();
++
++    // sampleCol or sampleRow is the width/height allocated to each residue
++    // in particular, sometimes we may need more than one row/col of the
++    // BufferedImage allocated
++    // sampleCol is how much of a residue to assign to each pixel
++    // sampleRow is how many sequences to assign to each pixel
++    float sampleCol = alwidth / (float) od.getWidth();
++    float sampleRow = alheight / (float) od.getSequencesHeight();
++
++    buildImage(sampleRow, sampleCol);
++=======
++    float sampleCol = (float) alwidth / (float) width;
++    float sampleRow = (float) alheight / (float) sequencesHeight;
++
++    int lastcol = -1, lastrow = -1;
++    Color color = Color.white;
++    int row, col;
++    jalview.datamodel.SequenceI seq;
++    final boolean hasHiddenRows = av.hasHiddenRows(), hasHiddenCols = av
++            .hasHiddenColumns();
++    boolean hiddenRow = false;
++    // get hidden row and hidden column map once at beginning.
++    // clone featureRenderer settings to avoid race conditions... if state is
++    // updated just need to refresh again
++
++    FeatureColourFinder finder = new FeatureColourFinder(fr);
++
++    for (row = 0; row < sequencesHeight; row++)
++    {
++      if (resizeAgain)
++      {
++        break;
++      }
++      if ((int) (row * sampleRow) == lastrow)
++      {
++        // No need to recalculate the colours,
++        // Just copy from the row above
++        for (col = 0; col < width; col++)
++        {
++          if (resizeAgain)
++          {
++            break;
++          }
++          miniMe.setRGB(col, row, miniMe.getRGB(col, row - 1));
++        }
++        continue;
++      }
++
++      lastrow = (int) (row * sampleRow);
++
++      hiddenRow = false;
++      if (hasHiddenRows)
++      {
++        seq = av.getAlignment().getHiddenSequences()
++                .getHiddenSequence(lastrow);
++        if (seq == null)
++        {
++          int index = av.getAlignment().getHiddenSequences()
++                  .findIndexWithoutHiddenSeqs(lastrow);
++
++          seq = av.getAlignment().getSequenceAt(index);
++        }
++        else
++        {
++          hiddenRow = true;
++        }
++      }
++      else
++      {
++        seq = av.getAlignment().getSequenceAt(lastrow);
++      }
++
++      if (seq == null)
++      {
++        System.out.println(lastrow + " null");
++        continue;
++      }
++
++      for (col = 0; col < width; col++)
++      {
++        if (resizeAgain)
++        {
++          break;
++        }
++        if ((int) (col * sampleCol) == lastcol
++                && (int) (row * sampleRow) == lastrow)
++        {
++          miniMe.setRGB(col, row, color.getRGB());
++          continue;
++        }
++
++        lastcol = (int) (col * sampleCol);
++
++        if (seq.getLength() > lastcol)
++        {
++          color = sr.getResidueColour(seq, lastcol, finder);
++        }
++        else
++        {
++          color = Color.WHITE;
++        }
++
++        if (hiddenRow
++                || (hasHiddenCols && !av.getColumnSelection().isVisible(
++                        lastcol)))
++        {
++          color = color.darker().darker();
++        }
++
++        miniMe.setRGB(col, row, color.getRGB());
++>>>>>>> bug/JAL-2436featureRendererThreading
++
++    if (av.isShowAnnotation())
++    {
++      renderer.updateFromAlignViewport(av);
++      for (int col = 0; col < od.getWidth() && !resizeAgain; col++)
++      {
++        mg.translate(col, od.getSequencesHeight());
++        renderer.drawGraph(mg, av.getAlignmentConservationAnnotation(),
++                av.getAlignmentConservationAnnotation().annotations,
++                (int) (sampleCol) + 1, od.getGraphHeight(),
++                (int) (col * sampleCol), (int) (col * sampleCol) + 1);
++        mg.translate(-col, -od.getSequencesHeight());
++
++      }
++    }
++    System.gc();
++
++    resizing = false;
++
++    if (resizeAgain)
++    {
++      resizeAgain = false;
++      updateOverviewImage();
++    }
++    else
++    {
++      lastMiniMe = miniMe;
++    }
++
++    setBoxPosition();
++  }
++
++  /*
++   * Build the overview panel image
++   */
++  private void buildImage(float sampleRow, float sampleCol)
++  {
++    int lastcol = -1;
++    int lastrow = -1;
++    int color = Color.white.getRGB();
++
++    SequenceI seq = null;
++
++    final boolean hasHiddenCols = av.hasHiddenColumns();
++    boolean hiddenRow = false;
++    // get hidden row and hidden column map once at beginning.
++    // clone featureRenderer settings to avoid race conditions... if state is
++    // updated just need to refresh again
++    for (int row = 0; row < od.getSequencesHeight() && !resizeAgain; row++)
++    {
++      boolean doCopy = true;
++      int currentrow = (int) (row * sampleRow);
++      if (currentrow != lastrow)
++      {
++        doCopy = false;
++
++        lastrow = currentrow;
++
++        // get the sequence which would be at alignment index 'lastrow' if no
++        // rows were hidden, and determine whether it is hidden or not
++        hiddenRow = av.getAlignment().isHidden(lastrow);
++        seq = av.getAlignment().getSequenceAtAbsoluteIndex(lastrow);
++      }
++
++      for (int col = 0; col < od.getWidth() && !resizeAgain; col++)
++      {
++        if (doCopy)
++        {
++          color = miniMe.getRGB(col, row - 1);
++        }
++        else if ((int) (col * sampleCol) != lastcol
++                || (int) (row * sampleRow) != lastrow)
++        {
++          lastcol = (int) (col * sampleCol);
++          color = getColumnColourFromSequence(seq, hiddenRow, hasHiddenCols,
++                  lastcol);
++        }
++        // else we just use the color we already have , so don't need to set it
++
++        miniMe.setRGB(col, row, color);
++      }
++    }
++  }
++
++  /*
++   * Find the colour of a sequence at a specified column position
++   */
++  private int getColumnColourFromSequence(jalview.datamodel.SequenceI seq,
++          boolean hiddenRow, boolean hasHiddenCols, int lastcol)
++  {
++    int color;
++
++    if (seq == null)
++    {
++      color = Color.white.getRGB();
++    }
++    else if (seq.getLength() > lastcol)
++    {
++      color = sr.getResidueBoxColour(seq, lastcol).getRGB();
++
++      if (av.isShowSequenceFeatures())
++      {
++        color = fr.findFeatureColour(color, seq, lastcol);
++      }
++    }
++    else
++    {
++      color = Color.white.getRGB();
++    }
++
++    if (hiddenRow
++            || (hasHiddenCols && !av.getColumnSelection()
++                    .isVisible(lastcol)))
++    {
++      color = new Color(color).darker().darker().getRGB();
++    }
++
++    return color;
++  }
++
++  /**
++   * Update the overview panel box when the associated alignment panel is
++   * changed
++   * 
++   */
++  public void setBoxPosition()
++  {
++    od.setBoxPosition(av.getAlignment()
++            .getHiddenSequences(), av.getColumnSelection(), av.getRanges());
++    repaint();
++  }
++
++
++  @Override
++  public void paintComponent(Graphics g)
++  {
++    if (resizing || resizeAgain)
++    {
++      if (lastMiniMe == null)
++      {
++        g.setColor(Color.white);
++        g.fillRect(0, 0, getWidth(), getHeight());
++      }
++      else
++      {
++        g.drawImage(lastMiniMe, 0, 0, getWidth(), getHeight(), this);
++      }
++      g.setColor(TRANS_GREY);
++      g.fillRect(0, 0, getWidth(), getHeight());
++    }
++    else if (lastMiniMe != null)
++    {
++      g.drawImage(lastMiniMe, 0, 0, this);
++      if (lastMiniMe != miniMe)
++      {
++        g.setColor(TRANS_GREY);
++        g.fillRect(0, 0, getWidth(), getHeight());
++      }
++    }
++
++    g.setColor(Color.red);
++    od.drawBox(g);
++  }
++}
Simple merge
index 0000000,0000000..c0a86df
new file mode 100755 (executable)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,980 @@@
++/*
++ * 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.gui;
++
++import jalview.datamodel.AlignmentI;
++import jalview.datamodel.SearchResults;
++import jalview.datamodel.SequenceGroup;
++import jalview.datamodel.SequenceI;
++
++import java.awt.BasicStroke;
++import java.awt.BorderLayout;
++import java.awt.Color;
++import java.awt.FontMetrics;
++import java.awt.Graphics;
++import java.awt.Graphics2D;
++import java.awt.RenderingHints;
++import java.awt.Shape;
++import java.awt.image.BufferedImage;
++import java.util.List;
++
++import javax.swing.JComponent;
++
++/**
++ * DOCUMENT ME!
++ * 
++ * @author $author$
++ * @version $Revision$
++ */
++public class SeqCanvas extends JComponent
++{
++  final FeatureRenderer fr;
++
++  final SequenceRenderer sr;
++
++  BufferedImage img;
++
++  Graphics2D gg;
++
++  int imgWidth;
++
++  int imgHeight;
++
++  AlignViewport av;
++
++  SearchResults searchResults = null;
++
++  boolean fastPaint = false;
++
++  int LABEL_WEST;
++
++  int LABEL_EAST;
++
++  int cursorX = 0;
++
++  int cursorY = 0;
++
++  /**
++   * Creates a new SeqCanvas object.
++   * 
++   * @param av
++   *          DOCUMENT ME!
++   */
++  public SeqCanvas(AlignmentPanel ap)
++  {
++    this.av = ap.av;
++    updateViewport();
++    fr = new FeatureRenderer(ap);
++    sr = new SequenceRenderer(av);
++    setLayout(new BorderLayout());
++    PaintRefresher.Register(this, av.getSequenceSetId());
++    setBackground(Color.white);
++  }
++
++  public SequenceRenderer getSequenceRenderer()
++  {
++    return sr;
++  }
++
++  public FeatureRenderer getFeatureRenderer()
++  {
++    return fr;
++  }
++
++  int charHeight = 0, charWidth = 0;
++
++  private void updateViewport()
++  {
++    charHeight = av.getCharHeight();
++    charWidth = av.getCharWidth();
++  }
++
++  /**
++   * DOCUMENT ME!
++   * 
++   * @param g
++   *          DOCUMENT ME!
++   * @param startx
++   *          DOCUMENT ME!
++   * @param endx
++   *          DOCUMENT ME!
++   * @param ypos
++   *          DOCUMENT ME!
++   */
++  private void drawNorthScale(Graphics g, int startx, int endx, int ypos)
++  {
++    updateViewport();
++    int scalestartx = startx - (startx % 10) + 10;
++
++    g.setColor(Color.black);
++    // NORTH SCALE
++    for (int i = scalestartx; i < endx; i += 10)
++    {
++      int value = i;
++      if (av.hasHiddenColumns())
++      {
++        value = av.getColumnSelection().adjustForHiddenColumns(value);
++      }
++
++      g.drawString(String.valueOf(value), (i - startx - 1) * charWidth,
++              ypos - (charHeight / 2));
++
++      g.drawLine(((i - startx - 1) * charWidth) + (charWidth / 2),
++              (ypos + 2) - (charHeight / 2), ((i - startx - 1) * charWidth)
++                      + (charWidth / 2), ypos - 2);
++    }
++  }
++
++  /**
++   * DOCUMENT ME!
++   * 
++   * @param g
++   *          DOCUMENT ME!
++   * @param startx
++   *          DOCUMENT ME!
++   * @param endx
++   *          DOCUMENT ME!
++   * @param ypos
++   *          DOCUMENT ME!
++   */
++  void drawWestScale(Graphics g, int startx, int endx, int ypos)
++  {
++    FontMetrics fm = getFontMetrics(av.getFont());
++    ypos += charHeight;
++
++    if (av.hasHiddenColumns())
++    {
++      startx = av.getColumnSelection().adjustForHiddenColumns(startx);
++      endx = av.getColumnSelection().adjustForHiddenColumns(endx);
++    }
++
++    int maxwidth = av.getAlignment().getWidth();
++    if (av.hasHiddenColumns())
++    {
++      maxwidth = av.getColumnSelection().findColumnPosition(maxwidth) - 1;
++    }
++
++    // WEST SCALE
++    for (int i = 0; i < av.getAlignment().getHeight(); i++)
++    {
++      SequenceI seq = av.getAlignment().getSequenceAt(i);
++      int index = startx;
++      int value = -1;
++
++      while (index < endx)
++      {
++        if (jalview.util.Comparison.isGap(seq.getCharAt(index)))
++        {
++          index++;
++
++          continue;
++        }
++
++        value = av.getAlignment().getSequenceAt(i).findPosition(index);
++
++        break;
++      }
++
++      if (value != -1)
++      {
++        int x = LABEL_WEST - fm.stringWidth(String.valueOf(value))
++                - charWidth / 2;
++        g.drawString(value + "", x, (ypos + (i * charHeight))
++                - (charHeight / 5));
++      }
++    }
++  }
++
++  /**
++   * DOCUMENT ME!
++   * 
++   * @param g
++   *          DOCUMENT ME!
++   * @param startx
++   *          DOCUMENT ME!
++   * @param endx
++   *          DOCUMENT ME!
++   * @param ypos
++   *          DOCUMENT ME!
++   */
++  void drawEastScale(Graphics g, int startx, int endx, int ypos)
++  {
++    ypos += charHeight;
++
++    if (av.hasHiddenColumns())
++    {
++      endx = av.getColumnSelection().adjustForHiddenColumns(endx);
++    }
++
++    SequenceI seq;
++    // EAST SCALE
++    for (int i = 0; i < av.getAlignment().getHeight(); i++)
++    {
++      seq = av.getAlignment().getSequenceAt(i);
++      int index = endx;
++      int value = -1;
++
++      while (index > startx)
++      {
++        if (jalview.util.Comparison.isGap(seq.getCharAt(index)))
++        {
++          index--;
++
++          continue;
++        }
++
++        value = seq.findPosition(index);
++
++        break;
++      }
++
++      if (value != -1)
++      {
++        g.drawString(String.valueOf(value), 0, (ypos + (i * charHeight))
++                - (charHeight / 5));
++      }
++    }
++  }
++
++  boolean fastpainting = false;
++
++  /**
++   * need to make this thread safe move alignment rendering in response to
++   * slider adjustment
++   * 
++   * @param horizontal
++   *          shift along
++   * @param vertical
++   *          shift up or down in repaint
++   */
++  public void fastPaint(int horizontal, int vertical)
++  {
++    if (fastpainting || gg == null)
++    {
++      return;
++    }
++    fastpainting = true;
++    fastPaint = true;
++    updateViewport();
++    gg.copyArea(horizontal * charWidth, vertical * charHeight, imgWidth,
++            imgHeight, -horizontal * charWidth, -vertical * charHeight);
++
++    int sr = av.startRes;
++    int er = av.endRes;
++    int ss = av.startSeq;
++    int es = av.endSeq;
++    int transX = 0;
++    int transY = 0;
++
++    if (horizontal > 0) // scrollbar pulled right, image to the left
++    {
++      er++;
++      transX = (er - sr - horizontal) * charWidth;
++      sr = er - horizontal;
++    }
++    else if (horizontal < 0)
++    {
++      er = sr - horizontal - 1;
++    }
++    else if (vertical > 0) // scroll down
++    {
++      ss = es - vertical;
++
++      if (ss < av.startSeq)
++      { // ie scrolling too fast, more than a page at a time
++        ss = av.startSeq;
++      }
++      else
++      {
++        transY = imgHeight - (vertical * charHeight);
++      }
++    }
++    else if (vertical < 0)
++    {
++      es = ss - vertical;
++
++      if (es > av.endSeq)
++      {
++        es = av.endSeq;
++      }
++    }
++
++    gg.translate(transX, transY);
++    drawPanel(gg, sr, er, ss, es, 0);
++    gg.translate(-transX, -transY);
++
++    repaint();
++    fastpainting = false;
++  }
++
++  /**
++   * Definitions of startx and endx (hopefully): SMJS This is what I'm working
++   * towards! startx is the first residue (starting at 0) to display. endx is
++   * the last residue to display (starting at 0). starty is the first sequence
++   * to display (starting at 0). endy is the last sequence to display (starting
++   * at 0). NOTE 1: The av limits are set in setFont in this class and in the
++   * adjustment listener in SeqPanel when the scrollbars move.
++   */
++
++  // Set this to false to force a full panel paint
++  @Override
++  public void paintComponent(Graphics g)
++  {
++    updateViewport();
++    BufferedImage lcimg = img; // take reference since other threads may null
++    // img and call later.
++    super.paintComponent(g);
++
++    if (lcimg != null
++            && (fastPaint
++                    || (getVisibleRect().width != g.getClipBounds().width) || (getVisibleRect().height != g
++                    .getClipBounds().height)))
++    {
++      g.drawImage(lcimg, 0, 0, this);
++      fastPaint = false;
++      return;
++    }
++
++    // this draws the whole of the alignment
++    imgWidth = getWidth();
++    imgHeight = getHeight();
++
++    imgWidth -= (imgWidth % charWidth);
++    imgHeight -= (imgHeight % charHeight);
++
++    if ((imgWidth < 1) || (imgHeight < 1))
++    {
++      return;
++    }
++
++    if (lcimg == null || imgWidth != lcimg.getWidth()
++            || imgHeight != lcimg.getHeight())
++    {
++      try
++      {
++        lcimg = img = new BufferedImage(imgWidth, imgHeight,
++                BufferedImage.TYPE_INT_RGB);
++        gg = (Graphics2D) img.getGraphics();
++        gg.setFont(av.getFont());
++      } catch (OutOfMemoryError er)
++      {
++        System.gc();
++        System.err.println("SeqCanvas OutOfMemory Redraw Error.\n" + er);
++        new OOMWarning("Creating alignment image for display", er);
++
++        return;
++      }
++    }
++
++    if (av.antiAlias)
++    {
++      gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
++              RenderingHints.VALUE_ANTIALIAS_ON);
++    }
++
++    gg.setColor(Color.white);
++    gg.fillRect(0, 0, imgWidth, imgHeight);
++
++    if (av.getWrapAlignment())
++    {
++      drawWrappedPanel(gg, getWidth(), getHeight(), av.startRes);
++    }
++    else
++    {
++      drawPanel(gg, av.startRes, av.endRes, av.startSeq, av.endSeq, 0);
++    }
++
++    g.drawImage(lcimg, 0, 0, this);
++
++  }
++
++  /**
++   * DOCUMENT ME!
++   * 
++   * @param cwidth
++   *          DOCUMENT ME!
++   * 
++   * @return DOCUMENT ME!
++   */
++  public int getWrappedCanvasWidth(int cwidth)
++  {
++    FontMetrics fm = getFontMetrics(av.getFont());
++
++    LABEL_EAST = 0;
++    LABEL_WEST = 0;
++
++    if (av.getScaleRightWrapped())
++    {
++      LABEL_EAST = fm.stringWidth(getMask());
++    }
++
++    if (av.getScaleLeftWrapped())
++    {
++      LABEL_WEST = fm.stringWidth(getMask());
++    }
++
++    return (cwidth - LABEL_EAST - LABEL_WEST) / charWidth;
++  }
++
++  /**
++   * Generates a string of zeroes.
++   * 
++   * @return String
++   */
++  String getMask()
++  {
++    String mask = "00";
++    int maxWidth = 0;
++    int tmp;
++    for (int i = 0; i < av.getAlignment().getHeight(); i++)
++    {
++      tmp = av.getAlignment().getSequenceAt(i).getEnd();
++      if (tmp > maxWidth)
++      {
++        maxWidth = tmp;
++      }
++    }
++
++    for (int i = maxWidth; i > 0; i /= 10)
++    {
++      mask += "0";
++    }
++    return mask;
++  }
++
++  /**
++   * DOCUMENT ME!
++   * 
++   * @param g
++   *          DOCUMENT ME!
++   * @param canvasWidth
++   *          DOCUMENT ME!
++   * @param canvasHeight
++   *          DOCUMENT ME!
++   * @param startRes
++   *          DOCUMENT ME!
++   */
++  public void drawWrappedPanel(Graphics g, int canvasWidth,
++          int canvasHeight, int startRes)
++  {
++    updateViewport();
++    AlignmentI al = av.getAlignment();
++
++    FontMetrics fm = getFontMetrics(av.getFont());
++
++    if (av.getScaleRightWrapped())
++    {
++      LABEL_EAST = fm.stringWidth(getMask());
++    }
++
++    if (av.getScaleLeftWrapped())
++    {
++      LABEL_WEST = fm.stringWidth(getMask());
++    }
++
++    int hgap = charHeight;
++    if (av.getScaleAboveWrapped())
++    {
++      hgap += charHeight;
++    }
++
++    int cWidth = (canvasWidth - LABEL_EAST - LABEL_WEST) / charWidth;
++    int cHeight = av.getAlignment().getHeight() * charHeight;
++
++    av.setWrappedWidth(cWidth);
++
++    av.endRes = av.startRes + cWidth;
++
++    int endx;
++    int ypos = hgap;
++    int maxwidth = av.getAlignment().getWidth() - 1;
++
++    if (av.hasHiddenColumns())
++    {
++      maxwidth = av.getColumnSelection().findColumnPosition(maxwidth) - 1;
++    }
++
++    while ((ypos <= canvasHeight) && (startRes < maxwidth))
++    {
++      endx = startRes + cWidth - 1;
++
++      if (endx > maxwidth)
++      {
++        endx = maxwidth;
++      }
++
++      g.setFont(av.getFont());
++      g.setColor(Color.black);
++
++      if (av.getScaleLeftWrapped())
++      {
++        drawWestScale(g, startRes, endx, ypos);
++      }
++
++      if (av.getScaleRightWrapped())
++      {
++        g.translate(canvasWidth - LABEL_EAST, 0);
++        drawEastScale(g, startRes, endx, ypos);
++        g.translate(-(canvasWidth - LABEL_EAST), 0);
++      }
++
++      g.translate(LABEL_WEST, 0);
++
++      if (av.getScaleAboveWrapped())
++      {
++        drawNorthScale(g, startRes, endx, ypos);
++      }
++
++      if (av.hasHiddenColumns() && av.getShowHiddenMarkers())
++      {
++        g.setColor(Color.blue);
++        int res;
++        for (int i = 0; i < av.getColumnSelection().getHiddenColumns()
++                .size(); i++)
++        {
++          res = av.getColumnSelection().findHiddenRegionPosition(i)
++                  - startRes;
++
++          if (res < 0 || res > endx - startRes)
++          {
++            continue;
++          }
++
++          gg.fillPolygon(
++                  new int[] { res * charWidth - charHeight / 4,
++                      res * charWidth + charHeight / 4, res * charWidth },
++                  new int[] { ypos - (charHeight / 2),
++                      ypos - (charHeight / 2), ypos - (charHeight / 2) + 8 },
++                  3);
++
++        }
++      }
++
++      // When printing we have an extra clipped region,
++      // the Printable page which we need to account for here
++      Shape clip = g.getClip();
++
++      if (clip == null)
++      {
++        g.setClip(0, 0, cWidth * charWidth, canvasHeight);
++      }
++      else
++      {
++        g.setClip(0, (int) clip.getBounds().getY(), cWidth * charWidth,
++                (int) clip.getBounds().getHeight());
++      }
++
++      drawPanel(g, startRes, endx, 0, al.getHeight(), ypos);
++
++      if (av.isShowAnnotation())
++      {
++        g.translate(0, cHeight + ypos + 3);
++        if (annotations == null)
++        {
++          annotations = new AnnotationPanel(av);
++        }
++
++        annotations.renderer.drawComponent(annotations, av, g, -1,
++                startRes, endx + 1);
++        g.translate(0, -cHeight - ypos - 3);
++      }
++      g.setClip(clip);
++      g.translate(-LABEL_WEST, 0);
++
++      ypos += cHeight + getAnnotationHeight() + hgap;
++
++      startRes += cWidth;
++    }
++  }
++
++  AnnotationPanel annotations;
++
++  int getAnnotationHeight()
++  {
++    if (!av.isShowAnnotation())
++    {
++      return 0;
++    }
++
++    if (annotations == null)
++    {
++      annotations = new AnnotationPanel(av);
++    }
++
++    return annotations.adjustPanelHeight();
++  }
++
++  /**
++   * DOCUMENT ME!
++   * 
++   * @param g1
++   *          DOCUMENT ME!
++   * @param startRes
++   *          DOCUMENT ME!
++   * @param endRes
++   *          DOCUMENT ME!
++   * @param startSeq
++   *          DOCUMENT ME!
++   * @param endSeq
++   *          DOCUMENT ME!
++   * @param offset
++   *          DOCUMENT ME!
++   */
++  public void drawPanel(Graphics g1, int startRes, int endRes,
++          int startSeq, int endSeq, int offset)
++  {
++    updateViewport();
++    if (!av.hasHiddenColumns())
++    {
++      draw(g1, startRes, endRes, startSeq, endSeq, offset);
++    }
++    else
++    {
++      List<int[]> regions = av.getColumnSelection().getHiddenColumns();
++
++      int screenY = 0;
++      int blockStart = startRes;
++      int blockEnd = endRes;
++      int newY = 0, clip;
++      for (int[] region : regions)
++      {
++        int hideStart = region[0];
++        int hideEnd = region[1];
++
++        if (hideStart < blockStart)
++        {
++          blockStart += (hideEnd - hideStart) + 1;
++          continue;
++        }
++        blockEnd = hideStart - 1;
++
++        g1.translate(screenY * charWidth, 0);
++
++        // find end of this visible block
++        newY += blockEnd - blockStart + 1;
++
++        clip = newY - (endRes - startRes);
++        if (clip > 0)
++        {
++          blockEnd = blockStart + (endRes - startRes) - screenY;
++        }
++        // TODO: JAL-1722 - does this block need clipping ?
++        draw(g1, blockStart, blockEnd, startSeq, endSeq, offset);
++        // TODO: JAL-1722 - is this hidden marker visible ?
++        if (clip < -1 && av.getShowHiddenMarkers())
++        {
++          g1.setColor(Color.blue);
++
++          g1.drawLine((blockEnd - blockStart + 1) * charWidth - 1,
++                  0 + offset, (blockEnd - blockStart + 1) * charWidth - 1,
++                  (endSeq - startSeq) * charHeight + offset);
++        }
++
++        g1.translate(-screenY * charWidth, 0);
++
++        screenY = newY;
++        blockStart = hideEnd + 1;
++
++        if (clip > 0)
++        {
++          // already rendered last block
++          return;
++        }
++      }
++
++      if (screenY <= (endRes - startRes))
++      {
++        // remaining visible region to render
++        blockEnd = blockStart + (endRes - startRes) - screenY;
++        g1.translate(screenY * charWidth, 0);
++        draw(g1, blockStart, blockEnd, startSeq, endSeq, offset);
++
++        g1.translate(-screenY * charWidth, 0);
++      }
++    }
++  }
++
++  // int startRes, int endRes, int startSeq, int endSeq, int x, int y,
++  // int x1, int x2, int y1, int y2, int startx, int starty,
++  private void draw(Graphics g, int startRes, int endRes, int startSeq,
++          int endSeq, int offset)
++  {
++    g.setFont(av.getFont());
++    sr.prepare(g, av.isRenderGaps());
++
++    SequenceI nextSeq;
++
++    // / First draw the sequences
++    // ///////////////////////////
++    for (int i = startSeq; i < endSeq; i++)
++    {
++      nextSeq = av.getAlignment().getSequenceAt(i);
++      if (nextSeq == null)
++      {
++        // occasionally, a race condition occurs such that the alignment row is
++        // empty
++        continue;
++      }
++      sr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
++              startRes, endRes, offset + ((i - startSeq) * charHeight));
++
++      if (av.isShowSequenceFeatures())
++      {
++        fr.drawSequence(g, nextSeq, startRes, endRes, offset
++                + ((i - startSeq) * charHeight));
++      }
++
++      // / Highlight search Results once all sequences have been drawn
++      // ////////////////////////////////////////////////////////
++      if (searchResults != null)
++      {
++        int[] visibleResults = searchResults.getResults(nextSeq, startRes,
++                endRes);
++        if (visibleResults != null)
++        {
++          for (int r = 0; r < visibleResults.length; r += 2)
++          {
++            sr.drawHighlightedText(nextSeq, visibleResults[r],
++                    visibleResults[r + 1], (visibleResults[r] - startRes)
++                            * charWidth, offset
++                            + ((i - startSeq) * charHeight));
++          }
++        }
++      }
++
++      if (av.cursorMode && cursorY == i && cursorX >= startRes
++              && cursorX <= endRes)
++      {
++        sr.drawCursor(nextSeq, cursorX, (cursorX - startRes) * charWidth,
++                offset + ((i - startSeq) * charHeight));
++      }
++    }
++
++    if (av.getSelectionGroup() != null
++            || av.getAlignment().getGroups().size() > 0)
++    {
++      drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
++    }
++
++  }
++
++  void drawGroupsBoundaries(Graphics g1, int startRes, int endRes,
++          int startSeq, int endSeq, int offset)
++  {
++    Graphics2D g = (Graphics2D) g1;
++    //
++    // ///////////////////////////////////
++    // Now outline any areas if necessary
++    // ///////////////////////////////////
++    SequenceGroup group = av.getSelectionGroup();
++
++    int sx = -1;
++    int sy = -1;
++    int ex = -1;
++    int groupIndex = -1;
++    int visWidth = (endRes - startRes + 1) * charWidth;
++
++    if ((group == null) && (av.getAlignment().getGroups().size() > 0))
++    {
++      group = av.getAlignment().getGroups().get(0);
++      groupIndex = 0;
++    }
++
++    if (group != null)
++    {
++      do
++      {
++        int oldY = -1;
++        int i = 0;
++        boolean inGroup = false;
++        int top = -1;
++        int bottom = -1;
++
++        for (i = startSeq; i < endSeq; i++)
++        {
++          sx = (group.getStartRes() - startRes) * charWidth;
++          sy = offset + ((i - startSeq) * charHeight);
++          ex = (((group.getEndRes() + 1) - group.getStartRes()) * charWidth) - 1;
++
++          if (sx + ex < 0 || sx > visWidth)
++          {
++            continue;
++          }
++
++          if ((sx <= (endRes - startRes) * charWidth)
++                  && group.getSequences(null).contains(
++                          av.getAlignment().getSequenceAt(i)))
++          {
++            if ((bottom == -1)
++                    && !group.getSequences(null).contains(
++                            av.getAlignment().getSequenceAt(i + 1)))
++            {
++              bottom = sy + charHeight;
++            }
++
++            if (!inGroup)
++            {
++              if (((top == -1) && (i == 0))
++                      || !group.getSequences(null).contains(
++                              av.getAlignment().getSequenceAt(i - 1)))
++              {
++                top = sy;
++              }
++
++              oldY = sy;
++              inGroup = true;
++
++              if (group == av.getSelectionGroup())
++              {
++                g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
++                        BasicStroke.JOIN_ROUND, 3f, new float[] { 5f, 3f },
++                        0f));
++                g.setColor(Color.RED);
++              }
++              else
++              {
++                g.setStroke(new BasicStroke());
++                g.setColor(group.getOutlineColour());
++              }
++            }
++          }
++          else
++          {
++            if (inGroup)
++            {
++              if (sx >= 0 && sx < visWidth)
++              {
++                g.drawLine(sx, oldY, sx, sy);
++              }
++
++              if (sx + ex < visWidth)
++              {
++                g.drawLine(sx + ex, oldY, sx + ex, sy);
++              }
++
++              if (sx < 0)
++              {
++                ex += sx;
++                sx = 0;
++              }
++
++              if (sx + ex > visWidth)
++              {
++                ex = visWidth;
++              }
++
++              else if (sx + ex >= (endRes - startRes + 1) * charWidth)
++              {
++                ex = (endRes - startRes + 1) * charWidth;
++              }
++
++              if (top != -1)
++              {
++                g.drawLine(sx, top, sx + ex, top);
++                top = -1;
++              }
++
++              if (bottom != -1)
++              {
++                g.drawLine(sx, bottom, sx + ex, bottom);
++                bottom = -1;
++              }
++
++              inGroup = false;
++            }
++          }
++        }
++
++        if (inGroup)
++        {
++          sy = offset + ((i - startSeq) * charHeight);
++          if (sx >= 0 && sx < visWidth)
++          {
++            g.drawLine(sx, oldY, sx, sy);
++          }
++
++          if (sx + ex < visWidth)
++          {
++            g.drawLine(sx + ex, oldY, sx + ex, sy);
++          }
++
++          if (sx < 0)
++          {
++            ex += sx;
++            sx = 0;
++          }
++
++          if (sx + ex > visWidth)
++          {
++            ex = visWidth;
++          }
++          else if (sx + ex >= (endRes - startRes + 1) * charWidth)
++          {
++            ex = (endRes - startRes + 1) * charWidth;
++          }
++
++          if (top != -1)
++          {
++            g.drawLine(sx, top, sx + ex, top);
++            top = -1;
++          }
++
++          if (bottom != -1)
++          {
++            g.drawLine(sx, bottom - 1, sx + ex, bottom - 1);
++            bottom = -1;
++          }
++
++          inGroup = false;
++        }
++
++        groupIndex++;
++
++        g.setStroke(new BasicStroke());
++
++        if (groupIndex >= av.getAlignment().getGroups().size())
++        {
++          break;
++        }
++
++        group = av.getAlignment().getGroups().get(groupIndex);
++
++      } while (groupIndex < av.getAlignment().getGroups().size());
++
++    }
++
++  }
++
++  /**
++   * DOCUMENT ME!
++   * 
++   * @param results
++   *          DOCUMENT ME!
++   */
++  public void highlightSearchResults(SearchResults results)
++  {
++    img = null;
++
++    searchResults = results;
++
++    repaint();
++  }
++}
index 0000000,0000000..5d6bef1
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,1161 @@@
++/*
++ * 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.gui;
++
++import jalview.bin.Jalview;
++import jalview.datamodel.DBRefEntry;
++import jalview.datamodel.DBRefSource;
++import jalview.datamodel.PDBEntry;
++import jalview.datamodel.SequenceI;
++import jalview.fts.api.FTSData;
++import jalview.fts.api.FTSDataColumnI;
++import jalview.fts.api.FTSRestClientI;
++import jalview.fts.core.FTSRestRequest;
++import jalview.fts.core.FTSRestResponse;
++import jalview.fts.service.pdb.PDBFTSRestClient;
++import jalview.jbgui.GStructureChooser;
++import jalview.structure.StructureSelectionManager;
++import jalview.util.MessageManager;
++import jalview.ws.DBRefFetcher;
++import jalview.ws.sifts.SiftsSettings;
++
++import java.awt.event.ItemEvent;
++import java.util.ArrayList;
++import java.util.Collection;
++import java.util.HashSet;
++import java.util.LinkedHashSet;
++import java.util.List;
++import java.util.Objects;
++import java.util.Vector;
++
++import javax.swing.JCheckBox;
++import javax.swing.JComboBox;
++import javax.swing.JLabel;
++import javax.swing.JOptionPane;
++import javax.swing.table.AbstractTableModel;
++
++/**
++ * Provides the behaviors for the Structure chooser Panel
++ * 
++ * @author tcnofoegbu
++ *
++ */
++@SuppressWarnings("serial")
++public class StructureChooser extends GStructureChooser implements
++        IProgressIndicator
++{
++  private boolean structuresDiscovered = false;
++
++  private SequenceI selectedSequence;
++
++  private SequenceI[] selectedSequences;
++
++  private IProgressIndicator progressIndicator;
++
++  private Collection<FTSData> discoveredStructuresSet;
++
++  private FTSRestRequest lastPdbRequest;
++
++  private FTSRestClientI pdbRestCleint;
++
++  private String selectedPdbFileName;
++
++  private boolean isValidPBDEntry;
++
++  public StructureChooser(SequenceI[] selectedSeqs, SequenceI selectedSeq,
++          AlignmentPanel ap)
++  {
++    this.ap = ap;
++    this.selectedSequence = selectedSeq;
++    this.selectedSequences = selectedSeqs;
++    this.progressIndicator = (ap == null) ? null : ap.alignFrame;
++    init();
++  }
++
++  /**
++   * Initializes parameters used by the Structure Chooser Panel
++   */
++  public void init()
++  {
++    if (!Jalview.isHeadlessMode())
++    {
++      progressBar = new ProgressBar(this.statusPanel, this.statusBar);
++    }
++
++    Thread discoverPDBStructuresThread = new Thread(new Runnable()
++    {
++      @Override
++      public void run()
++      {
++        long startTime = System.currentTimeMillis();
++        updateProgressIndicator(MessageManager
++                .getString("status.loading_cached_pdb_entries"), startTime);
++        loadLocalCachedPDBEntries();
++        updateProgressIndicator(null, startTime);
++        updateProgressIndicator(MessageManager
++                .getString("status.searching_for_pdb_structures"),
++                startTime);
++        fetchStructuresMetaData();
++        populateFilterComboBox();
++        updateProgressIndicator(null, startTime);
++        mainFrame.setVisible(true);
++        updateCurrentView();
++      }
++    });
++    discoverPDBStructuresThread.start();
++  }
++
++  /**
++   * Updates the progress indicator with the specified message
++   * 
++   * @param message
++   *          displayed message for the operation
++   * @param id
++   *          unique handle for this indicator
++   */
++  public void updateProgressIndicator(String message, long id)
++  {
++    if (progressIndicator != null)
++    {
++      progressIndicator.setProgressBar(message, id);
++    }
++  }
++
++  /**
++   * Retrieve meta-data for all the structure(s) for a given sequence(s) in a
++   * selection group
++   */
++  public void fetchStructuresMetaData()
++  {
++    long startTime = System.currentTimeMillis();
++    pdbRestCleint = PDBFTSRestClient.getInstance();
++    Collection<FTSDataColumnI> wantedFields = pdbDocFieldPrefs
++            .getStructureSummaryFields();
++
++    discoveredStructuresSet = new LinkedHashSet<FTSData>();
++    HashSet<String> errors = new HashSet<String>();
++    for (SequenceI seq : selectedSequences)
++    {
++      FTSRestRequest pdbRequest = new FTSRestRequest();
++      pdbRequest.setAllowEmptySeq(false);
++      pdbRequest.setResponseSize(500);
++      pdbRequest.setFieldToSearchBy("(");
++      pdbRequest.setWantedFields(wantedFields);
++      pdbRequest.setSearchTerm(buildQuery(seq) + ")");
++      pdbRequest.setAssociatedSequence(seq);
++      FTSRestResponse resultList;
++      try
++      {
++        resultList = pdbRestCleint.executeRequest(pdbRequest);
++      } catch (Exception e)
++      {
++        e.printStackTrace();
++        errors.add(e.getMessage());
++        continue;
++      }
++      lastPdbRequest = pdbRequest;
++      if (resultList.getSearchSummary() != null
++              && !resultList.getSearchSummary().isEmpty())
++      {
++        discoveredStructuresSet.addAll(resultList.getSearchSummary());
++      }
++    }
++
++    int noOfStructuresFound = 0;
++    String totalTime = (System.currentTimeMillis() - startTime)
++            + " milli secs";
++    if (discoveredStructuresSet != null
++            && !discoveredStructuresSet.isEmpty())
++    {
++      getResultTable().setModel(
++              FTSRestResponse.getTableModel(lastPdbRequest,
++              discoveredStructuresSet));
++      structuresDiscovered = true;
++      noOfStructuresFound = discoveredStructuresSet.size();
++      mainFrame.setTitle(MessageManager.formatMessage(
++              "label.structure_chooser_no_of_structures",
++              noOfStructuresFound, totalTime));
++    }
++    else
++    {
++      mainFrame.setTitle(MessageManager
++              .getString("label.structure_chooser_manual_association"));
++      if (errors.size() > 0)
++      {
++        StringBuilder errorMsg = new StringBuilder();
++        for (String error : errors)
++        {
++          errorMsg.append(error).append("\n");
++        }
++        JOptionPane.showMessageDialog(this, errorMsg.toString(),
++                MessageManager.getString("label.pdb_web-service_error"),
++                JOptionPane.ERROR_MESSAGE);
++      }
++    }
++  }
++
++  public void loadLocalCachedPDBEntries()
++  {
++    ArrayList<CachedPDB> entries = new ArrayList<CachedPDB>();
++    for (SequenceI seq : selectedSequences)
++    {
++      if (seq.getDatasetSequence() != null
++              && seq.getDatasetSequence().getAllPDBEntries() != null)
++      {
++        for (PDBEntry pdbEntry : seq.getDatasetSequence()
++                .getAllPDBEntries())
++        {
++          if (pdbEntry.getFile() != null)
++          {
++            entries.add(new CachedPDB(seq, pdbEntry));
++          }
++        }
++      }
++    }
++
++    PDBEntryTableModel tableModelx = new PDBEntryTableModel(entries);
++    tbl_local_pdb.setModel(tableModelx);
++  }
++
++  /**
++   * Builds a query string for a given sequences using its DBRef entries
++   * 
++   * @param seq
++   *          the sequences to build a query for
++   * @return the built query string
++   */
++
++  public static String buildQuery(SequenceI seq)
++  {
++    boolean isPDBRefsFound = false;
++    boolean isUniProtRefsFound = false;
++    StringBuilder queryBuilder = new StringBuilder();
++    HashSet<String> seqRefs = new LinkedHashSet<String>();
++
++    if (seq.getAllPDBEntries() != null)
++    {
++      for (PDBEntry entry : seq.getAllPDBEntries())
++      {
++        if (isValidSeqName(entry.getId()))
++        {
++          queryBuilder.append("pdb_id")
++                  .append(":")
++.append(entry.getId().toLowerCase())
++                  .append(" OR ");
++          isPDBRefsFound = true;
++          // seqRefs.add(entry.getId());
++        }
++      }
++    }
++
++    if (seq.getDBRefs() != null && seq.getDBRefs().length != 0)
++    {
++      for (DBRefEntry dbRef : seq.getDBRefs())
++      {
++        if (isValidSeqName(getDBRefId(dbRef)))
++        {
++          if (dbRef.getSource().equalsIgnoreCase(DBRefSource.UNIPROT))
++          {
++            queryBuilder
++.append("uniprot_accession").append(":")
++                    .append(getDBRefId(dbRef))
++                    .append(" OR ");
++            queryBuilder
++.append("uniprot_id")
++                    .append(":")
++                    .append(getDBRefId(dbRef)).append(" OR ");
++            isUniProtRefsFound = true;
++          }
++          else if (dbRef.getSource().equalsIgnoreCase(DBRefSource.PDB))
++          {
++
++            queryBuilder.append("pdb_id")
++                    .append(":").append(getDBRefId(dbRef).toLowerCase())
++                    .append(" OR ");
++            isPDBRefsFound = true;
++          }
++          else
++          {
++            seqRefs.add(getDBRefId(dbRef));
++          }
++        }
++      }
++    }
++
++    if (!isPDBRefsFound && !isUniProtRefsFound)
++    {
++      String seqName = seq.getName();
++      seqName = sanitizeSeqName(seqName);
++      String[] names = seqName.toLowerCase().split("\\|");
++      for (String name : names)
++      {
++        // System.out.println("Found name : " + name);
++        name.trim();
++        if (isValidSeqName(name))
++        {
++          seqRefs.add(name);
++        }
++      }
++
++      for (String seqRef : seqRefs)
++      {
++        queryBuilder.append("text:").append(seqRef).append(" OR ");
++      }
++    }
++
++    int endIndex = queryBuilder.lastIndexOf(" OR ");
++    if (queryBuilder.toString().length() < 6)
++    {
++      return null;
++    }
++    String query = queryBuilder.toString().substring(0, endIndex);
++    return query;
++  }
++
++  /**
++   * Remove the following special characters from input string +, -, &, !, (, ),
++   * {, }, [, ], ^, ", ~, *, ?, :, \
++   * 
++   * @param seqName
++   * @return
++   */
++  static String sanitizeSeqName(String seqName)
++  {
++    Objects.requireNonNull(seqName);
++    return seqName.replaceAll("\\[\\d*\\]", "")
++            .replaceAll("[^\\dA-Za-z|_]", "").replaceAll("\\s+", "+");
++  }
++
++
++  /**
++   * Ensures sequence ref names are not less than 3 characters and does not
++   * contain a database name
++   * 
++   * @param seqName
++   * @return
++   */
++  public static boolean isValidSeqName(String seqName)
++  {
++    // System.out.println("seqName : " + seqName);
++    String ignoreList = "pdb,uniprot,swiss-prot";
++    if (seqName.length() < 3)
++    {
++      return false;
++    }
++    if (seqName.contains(":"))
++    {
++      return false;
++    }
++    seqName = seqName.toLowerCase();
++    for (String ignoredEntry : ignoreList.split(","))
++    {
++      if (seqName.contains(ignoredEntry))
++      {
++        return false;
++      }
++    }
++    return true;
++  }
++
++  public static String getDBRefId(DBRefEntry dbRef)
++  {
++    String ref = dbRef.getAccessionId().replaceAll("GO:", "");
++    return ref;
++  }
++
++  /**
++   * Filters a given list of discovered structures based on supplied argument
++   * 
++   * @param fieldToFilterBy
++   *          the field to filter by
++   */
++  public void filterResultSet(final String fieldToFilterBy)
++  {
++    Thread filterThread = new Thread(new Runnable()
++    {
++      @Override
++      public void run()
++      {
++        long startTime = System.currentTimeMillis();
++        pdbRestCleint = PDBFTSRestClient.getInstance();
++        lbl_loading.setVisible(true);
++        Collection<FTSDataColumnI> wantedFields = pdbDocFieldPrefs
++                .getStructureSummaryFields();
++        Collection<FTSData> filteredResponse = new HashSet<FTSData>();
++        HashSet<String> errors = new HashSet<String>();
++
++        for (SequenceI seq : selectedSequences)
++        {
++          FTSRestRequest pdbRequest = new FTSRestRequest();
++          if (fieldToFilterBy.equalsIgnoreCase("uniprot_coverage"))
++          {
++            pdbRequest.setAllowEmptySeq(false);
++            pdbRequest.setResponseSize(1);
++            pdbRequest.setFieldToSearchBy("(");
++            pdbRequest.setSearchTerm(buildQuery(seq) + ")");
++            pdbRequest.setWantedFields(wantedFields);
++            pdbRequest.setAssociatedSequence(seq);
++            pdbRequest.setFacet(true);
++            pdbRequest.setFacetPivot(fieldToFilterBy + ",entry_entity");
++            pdbRequest.setFacetPivotMinCount(1);
++          }
++          else
++          {
++            pdbRequest.setAllowEmptySeq(false);
++            pdbRequest.setResponseSize(1);
++            pdbRequest.setFieldToSearchBy("(");
++            pdbRequest.setFieldToSortBy(fieldToFilterBy,
++                    !chk_invertFilter.isSelected());
++            pdbRequest.setSearchTerm(buildQuery(seq) + ")");
++            pdbRequest.setWantedFields(wantedFields);
++            pdbRequest.setAssociatedSequence(seq);
++          }
++          FTSRestResponse resultList;
++          try
++          {
++            resultList = pdbRestCleint.executeRequest(pdbRequest);
++          } catch (Exception e)
++          {
++            e.printStackTrace();
++            errors.add(e.getMessage());
++            continue;
++          }
++          lastPdbRequest = pdbRequest;
++          if (resultList.getSearchSummary() != null
++                  && !resultList.getSearchSummary().isEmpty())
++          {
++            filteredResponse.addAll(resultList.getSearchSummary());
++          }
++        }
++
++        String totalTime = (System.currentTimeMillis() - startTime)
++                + " milli secs";
++        if (!filteredResponse.isEmpty())
++        {
++          final int filterResponseCount = filteredResponse.size();
++          Collection<FTSData> reorderedStructuresSet = new LinkedHashSet<FTSData>();
++          reorderedStructuresSet.addAll(filteredResponse);
++          reorderedStructuresSet.addAll(discoveredStructuresSet);
++          getResultTable().setModel(
++                  FTSRestResponse.getTableModel(
++                  lastPdbRequest, reorderedStructuresSet));
++
++          FTSRestResponse.configureTableColumn(getResultTable(),
++                  wantedFields);
++          getResultTable().getColumn("Ref Sequence").setPreferredWidth(120);
++          getResultTable().getColumn("Ref Sequence").setMinWidth(100);
++          getResultTable().getColumn("Ref Sequence").setMaxWidth(200);
++          // Update table selection model here
++          getResultTable().addRowSelectionInterval(0,
++                  filterResponseCount - 1);
++          mainFrame.setTitle(MessageManager.formatMessage(
++                  "label.structure_chooser_filter_time", totalTime));
++        }
++        else
++        {
++          mainFrame.setTitle(MessageManager.formatMessage(
++                  "label.structure_chooser_filter_time", totalTime));
++          if (errors.size() > 0)
++          {
++            StringBuilder errorMsg = new StringBuilder();
++            for (String error : errors)
++            {
++              errorMsg.append(error).append("\n");
++            }
++            JOptionPane.showMessageDialog(
++                    null,
++                    errorMsg.toString(),
++                    MessageManager.getString("label.pdb_web-service_error"),
++                    JOptionPane.ERROR_MESSAGE);
++          }
++        }
++
++        lbl_loading.setVisible(false);
++
++        validateSelections();
++      }
++    });
++    filterThread.start();
++  }
++
++  /**
++   * Handles action event for btn_pdbFromFile
++   */
++  @Override
++  public void pdbFromFile_actionPerformed()
++  {
++    jalview.io.JalviewFileChooser chooser = new jalview.io.JalviewFileChooser(
++            jalview.bin.Cache.getProperty("LAST_DIRECTORY"));
++    chooser.setFileView(new jalview.io.JalviewFileView());
++    chooser.setDialogTitle(MessageManager.formatMessage(
++            "label.select_pdb_file_for",
++            selectedSequence.getDisplayId(false)));
++    chooser.setToolTipText(MessageManager.formatMessage(
++            "label.load_pdb_file_associate_with_sequence",
++            selectedSequence.getDisplayId(false)));
++
++    int value = chooser.showOpenDialog(null);
++    if (value == jalview.io.JalviewFileChooser.APPROVE_OPTION)
++    {
++      selectedPdbFileName = chooser.getSelectedFile().getPath();
++      jalview.bin.Cache.setProperty("LAST_DIRECTORY", selectedPdbFileName);
++      validateSelections();
++    }
++  }
++
++  /**
++   * Populates the filter combo-box options dynamically depending on discovered
++   * structures
++   */
++  @Override
++  protected void populateFilterComboBox()
++  {
++    if (isStructuresDiscovered())
++    {
++      cmb_filterOption.addItem(new FilterOption("Best Quality",
++              "overall_quality", VIEWS_FILTER));
++      cmb_filterOption.addItem(new FilterOption("Best Resolution",
++              "resolution", VIEWS_FILTER));
++      cmb_filterOption.addItem(new FilterOption("Most Protein Chain",
++              "number_of_protein_chains", VIEWS_FILTER));
++      cmb_filterOption.addItem(new FilterOption("Most Bound Molecules",
++              "number_of_bound_molecules", VIEWS_FILTER));
++      cmb_filterOption.addItem(new FilterOption("Most Polymer Residues",
++              "number_of_polymer_residues", VIEWS_FILTER));
++    }
++    cmb_filterOption.addItem(new FilterOption("Enter PDB Id", "-",
++            VIEWS_ENTER_ID));
++    cmb_filterOption.addItem(new FilterOption("From File", "-",
++            VIEWS_FROM_FILE));
++    cmb_filterOption.addItem(new FilterOption("Cached PDB Entries", "-",
++            VIEWS_LOCAL_PDB));
++  }
++
++  /**
++   * Updates the displayed view based on the selected filter option
++   */
++  @Override
++  protected void updateCurrentView()
++  {
++    FilterOption selectedFilterOpt = ((FilterOption) cmb_filterOption
++            .getSelectedItem());
++    layout_switchableViews.show(pnl_switchableViews,
++            selectedFilterOpt.getView());
++    String filterTitle = mainFrame.getTitle();
++    mainFrame.setTitle(frameTitle);
++    chk_invertFilter.setVisible(false);
++    if (selectedFilterOpt.getView() == VIEWS_FILTER)
++    {
++      mainFrame.setTitle(filterTitle);
++      chk_invertFilter.setVisible(true);
++      filterResultSet(selectedFilterOpt.getValue());
++    }
++    else if (selectedFilterOpt.getView() == VIEWS_ENTER_ID
++            || selectedFilterOpt.getView() == VIEWS_FROM_FILE)
++    {
++      mainFrame.setTitle(MessageManager
++              .getString("label.structure_chooser_manual_association"));
++      idInputAssSeqPanel.loadCmbAssSeq();
++      fileChooserAssSeqPanel.loadCmbAssSeq();
++    }
++    validateSelections();
++  }
++
++  /**
++   * Validates user selection and activates the view button if all parameters
++   * are correct
++   */
++  @Override
++  public void validateSelections()
++  {
++    FilterOption selectedFilterOpt = ((FilterOption) cmb_filterOption
++            .getSelectedItem());
++    btn_view.setEnabled(false);
++    String currentView = selectedFilterOpt.getView();
++    if (currentView == VIEWS_FILTER)
++    {
++      if (getResultTable().getSelectedRows().length > 0)
++      {
++        btn_view.setEnabled(true);
++      }
++    }
++    else if (currentView == VIEWS_LOCAL_PDB)
++    {
++      if (tbl_local_pdb.getSelectedRows().length > 0)
++      {
++        btn_view.setEnabled(true);
++      }
++    }
++    else if (currentView == VIEWS_ENTER_ID)
++    {
++      validateAssociationEnterPdb();
++    }
++    else if (currentView == VIEWS_FROM_FILE)
++    {
++      validateAssociationFromFile();
++    }
++  }
++
++  /**
++   * Validates inputs from the Manual PDB entry panel
++   */
++  public void validateAssociationEnterPdb()
++  {
++    AssociateSeqOptions assSeqOpt = (AssociateSeqOptions) idInputAssSeqPanel
++            .getCmb_assSeq().getSelectedItem();
++    lbl_pdbManualFetchStatus.setIcon(errorImage);
++    lbl_pdbManualFetchStatus.setToolTipText("");
++    if (txt_search.getText().length() > 0)
++    {
++      lbl_pdbManualFetchStatus
++              .setToolTipText(JvSwingUtils.wrapTooltip(true, MessageManager
++                      .formatMessage("info.no_pdb_entry_found_for",
++                              txt_search.getText())));
++    }
++
++    if (errorWarning.length() > 0)
++    {
++      lbl_pdbManualFetchStatus.setIcon(warningImage);
++      lbl_pdbManualFetchStatus.setToolTipText(JvSwingUtils.wrapTooltip(
++              true, errorWarning.toString()));
++    }
++
++    if (selectedSequences.length == 1
++            || !assSeqOpt.getName().equalsIgnoreCase(
++                    "-Select Associated Seq-"))
++    {
++      txt_search.setEnabled(true);
++      if (isValidPBDEntry)
++      {
++        btn_view.setEnabled(true);
++        lbl_pdbManualFetchStatus.setToolTipText("");
++        lbl_pdbManualFetchStatus.setIcon(goodImage);
++      }
++    }
++    else
++    {
++      txt_search.setEnabled(false);
++      lbl_pdbManualFetchStatus.setIcon(errorImage);
++    }
++  }
++
++  /**
++   * Validates inputs for the manual PDB file selection options
++   */
++  public void validateAssociationFromFile()
++  {
++    AssociateSeqOptions assSeqOpt = (AssociateSeqOptions) fileChooserAssSeqPanel
++            .getCmb_assSeq().getSelectedItem();
++    lbl_fromFileStatus.setIcon(errorImage);
++    if (selectedSequences.length == 1
++            || (assSeqOpt != null && !assSeqOpt.getName().equalsIgnoreCase(
++                    "-Select Associated Seq-")))
++    {
++      btn_pdbFromFile.setEnabled(true);
++      if (selectedPdbFileName != null && selectedPdbFileName.length() > 0)
++      {
++        btn_view.setEnabled(true);
++        lbl_fromFileStatus.setIcon(goodImage);
++      }
++    }
++    else
++    {
++      btn_pdbFromFile.setEnabled(false);
++      lbl_fromFileStatus.setIcon(errorImage);
++    }
++  }
++
++  @Override
++  public void cmbAssSeqStateChanged()
++  {
++    validateSelections();
++  }
++
++  /**
++   * Handles the state change event for the 'filter' combo-box and 'invert'
++   * check-box
++   */
++  @Override
++  protected void stateChanged(ItemEvent e)
++  {
++    if (e.getSource() instanceof JCheckBox)
++    {
++      updateCurrentView();
++    }
++    else
++    {
++      if (e.getStateChange() == ItemEvent.SELECTED)
++      {
++        updateCurrentView();
++      }
++    }
++
++  }
++
++  /**
++   * Handles action event for btn_ok
++   */
++  @Override
++  public void ok_ActionPerformed()
++  {
++    final StructureSelectionManager ssm = ap.getStructureSelectionManager();
++    new Thread(new Runnable()
++    {
++      @Override
++      public void run()
++      {
++    FilterOption selectedFilterOpt = ((FilterOption) cmb_filterOption
++            .getSelectedItem());
++    String currentView = selectedFilterOpt.getView();
++    if (currentView == VIEWS_FILTER)
++    {
++          int pdbIdColIndex = getResultTable().getColumn("PDB Id")
++                  .getModelIndex();
++          int refSeqColIndex = getResultTable().getColumn("Ref Sequence")
++              .getModelIndex();
++          int[] selectedRows = getResultTable().getSelectedRows();
++      PDBEntry[] pdbEntriesToView = new PDBEntry[selectedRows.length];
++      int count = 0;
++      ArrayList<SequenceI> selectedSeqsToView = new ArrayList<SequenceI>();
++      for (int row : selectedRows)
++      {
++            String pdbIdStr = getResultTable().getValueAt(row,
++                    pdbIdColIndex)
++                .toString();
++            SequenceI selectedSeq = (SequenceI) getResultTable()
++                    .getValueAt(row,
++                refSeqColIndex);
++        selectedSeqsToView.add(selectedSeq);
++            PDBEntry pdbEntry = selectedSeq.getPDBEntry(pdbIdStr);
++            if (pdbEntry == null)
++            {
++              pdbEntry = getFindEntry(pdbIdStr,
++                      selectedSeq.getAllPDBEntries());
++            }
++        if (pdbEntry == null)
++        {
++          pdbEntry = new PDBEntry();
++          pdbEntry.setId(pdbIdStr);
++          pdbEntry.setType(PDBEntry.Type.PDB);
++          selectedSeq.getDatasetSequence().addPDBId(pdbEntry);
++        }
++        pdbEntriesToView[count++] = pdbEntry;
++      }
++      SequenceI[] selectedSeqs = selectedSeqsToView
++              .toArray(new SequenceI[selectedSeqsToView.size()]);
++          launchStructureViewer(ssm, pdbEntriesToView, ap,
++                  selectedSeqs);
++    }
++    else if (currentView == VIEWS_LOCAL_PDB)
++    {
++      int[] selectedRows = tbl_local_pdb.getSelectedRows();
++      PDBEntry[] pdbEntriesToView = new PDBEntry[selectedRows.length];
++      int count = 0;
++          int pdbIdColIndex = tbl_local_pdb.getColumn("PDB Id")
++                  .getModelIndex();
++      int refSeqColIndex = tbl_local_pdb.getColumn("Ref Sequence")
++              .getModelIndex();
++      ArrayList<SequenceI> selectedSeqsToView = new ArrayList<SequenceI>();
++      for (int row : selectedRows)
++      {
++        PDBEntry pdbEntry = (PDBEntry) tbl_local_pdb.getValueAt(row,
++                pdbIdColIndex);
++        pdbEntriesToView[count++] = pdbEntry;
++        SequenceI selectedSeq = (SequenceI) tbl_local_pdb.getValueAt(row,
++                refSeqColIndex);
++        selectedSeqsToView.add(selectedSeq);
++      }
++      SequenceI[] selectedSeqs = selectedSeqsToView
++              .toArray(new SequenceI[selectedSeqsToView.size()]);
++          launchStructureViewer(ssm, pdbEntriesToView, ap,
++                  selectedSeqs);
++    }
++    else if (currentView == VIEWS_ENTER_ID)
++    {
++      SequenceI userSelectedSeq = ((AssociateSeqOptions) idInputAssSeqPanel
++              .getCmb_assSeq().getSelectedItem()).getSequence();
++      if (userSelectedSeq != null)
++      {
++        selectedSequence = userSelectedSeq;
++      }
++
++      String pdbIdStr = txt_search.getText();
++      PDBEntry pdbEntry = selectedSequence.getPDBEntry(pdbIdStr);
++      if (pdbEntry == null)
++      {
++        pdbEntry = new PDBEntry();
++            if (pdbIdStr.split(":").length > 1)
++            {
++              pdbEntry.setChainCode(pdbIdStr.split(":")[1]);
++            }
++        pdbEntry.setId(pdbIdStr);
++        pdbEntry.setType(PDBEntry.Type.PDB);
++        selectedSequence.getDatasetSequence().addPDBId(pdbEntry);
++      }
++
++      PDBEntry[] pdbEntriesToView = new PDBEntry[] { pdbEntry };
++          launchStructureViewer(ssm, pdbEntriesToView, ap,
++                  new SequenceI[] { selectedSequence });
++    }
++    else if (currentView == VIEWS_FROM_FILE)
++    {
++      SequenceI userSelectedSeq = ((AssociateSeqOptions) fileChooserAssSeqPanel
++              .getCmb_assSeq().getSelectedItem()).getSequence();
++      if (userSelectedSeq != null)
++      {
++        selectedSequence = userSelectedSeq;
++      }
++      PDBEntry fileEntry = new AssociatePdbFileWithSeq()
++              .associatePdbWithSeq(selectedPdbFileName,
++                      jalview.io.AppletFormatAdapter.FILE,
++                      selectedSequence, true, Desktop.instance);
++
++          launchStructureViewer(ssm,
++                  new PDBEntry[] { fileEntry }, ap,
++                  new SequenceI[] { selectedSequence });
++    }
++    mainFrame.dispose();
++      }
++    }).start();
++  }
++
++  private PDBEntry getFindEntry(String id, Vector<PDBEntry> pdbEntries)
++  {
++    Objects.requireNonNull(id);
++    Objects.requireNonNull(pdbEntries);
++    PDBEntry foundEntry = null;
++    for (PDBEntry entry : pdbEntries)
++    {
++      if (entry.getId().equalsIgnoreCase(id))
++      {
++        return entry;
++      }
++    }
++    return foundEntry;
++  }
++
++  private void launchStructureViewer(StructureSelectionManager ssm,
++          final PDBEntry[] pdbEntriesToView,
++          final AlignmentPanel alignPanel, SequenceI[] sequences)
++  {
++    long progressId = sequences.hashCode();
++    setProgressBar(MessageManager
++            .getString("status.launching_3d_structure_viewer"), progressId);
++    final StructureViewer sViewer = new StructureViewer(ssm);
++    setProgressBar(null, progressId);
++
++    if (SiftsSettings.isMapWithSifts())
++    {
++      // TODO: prompt user if there are lots of sequences without dbrefs.
++      // It can take a long time if we need to fetch all dbrefs for all
++      // sequences!
++      ArrayList<SequenceI> seqsWithoutSourceDBRef = new ArrayList<SequenceI>();
++      for (SequenceI seq : sequences)
++      {
++        if (seq.getSourceDBRef() == null && seq.getDBRefs() == null)
++        {
++            seqsWithoutSourceDBRef.add(seq);
++            continue;
++          }
++      }
++      if (!seqsWithoutSourceDBRef.isEmpty())
++      {
++        int y = seqsWithoutSourceDBRef.size();
++        setProgressBar(MessageManager.formatMessage(
++                "status.fetching_dbrefs_for_sequences_without_valid_refs",
++                y,
++                progressId);
++        SequenceI[] seqWithoutSrcDBRef = new SequenceI[y];
++        int x = 0;
++        for (SequenceI fSeq : seqsWithoutSourceDBRef)
++        {
++          seqWithoutSrcDBRef[x++] = fSeq;
++        }
++        new DBRefFetcher(seqWithoutSrcDBRef).fetchDBRefs(true);
++        setProgressBar("Fetch complete.", progressId); // todo i18n
++      }
++    }
++    if (pdbEntriesToView.length > 1)
++    {
++      ArrayList<SequenceI[]> seqsMap = new ArrayList<SequenceI[]>();
++      for (SequenceI seq : sequences)
++      {
++        seqsMap.add(new SequenceI[] { seq });
++      }
++      SequenceI[][] collatedSeqs = seqsMap.toArray(new SequenceI[0][0]);
++<<<<<<< Updated upstream
++      ssm.setProgressBar(null);
++      ssm.setProgressBar(MessageManager
++              .getString("status.fetching_3d_structures_for_selected_entries"));
++=======
++      setProgressBar("Fetching structure data", progressId);
++>>>>>>> Stashed changes
++      sViewer.viewStructures(pdbEntriesToView, collatedSeqs, alignPanel);
++    }
++    else
++    {
++<<<<<<< Updated upstream
++      ssm.setProgressBar(null);
++      ssm.setProgressBar(MessageManager.formatMessage(
++              "status.fetching_3d_structures_for",
++              pdbEntriesToView[0].getId()));
++=======
++      setProgressBar(
++              "Fetching structure for " + pdbEntriesToView[0].getId(),
++              progressId);
++>>>>>>> Stashed changes
++      sViewer.viewStructures(pdbEntriesToView[0], sequences, alignPanel);
++    }
++    setProgressBar(null, progressId);
++  }
++
++  /**
++   * Populates the combo-box used in associating manually fetched structures to
++   * a unique sequence when more than one sequence selection is made.
++   */
++  @Override
++  public void populateCmbAssociateSeqOptions(
++          JComboBox<AssociateSeqOptions> cmb_assSeq, JLabel lbl_associateSeq)
++  {
++    cmb_assSeq.removeAllItems();
++    cmb_assSeq.addItem(new AssociateSeqOptions("-Select Associated Seq-",
++            null));
++    lbl_associateSeq.setVisible(false);
++    if (selectedSequences.length > 1)
++    {
++      for (SequenceI seq : selectedSequences)
++      {
++        cmb_assSeq.addItem(new AssociateSeqOptions(seq));
++      }
++    }
++    else
++    {
++      String seqName = selectedSequence.getDisplayId(false);
++      seqName = seqName.length() <= 40 ? seqName : seqName.substring(0, 39);
++      lbl_associateSeq.setText(seqName);
++      lbl_associateSeq.setVisible(true);
++      cmb_assSeq.setVisible(false);
++    }
++  }
++
++  public boolean isStructuresDiscovered()
++  {
++    return structuresDiscovered;
++  }
++
++  public void setStructuresDiscovered(boolean structuresDiscovered)
++  {
++    this.structuresDiscovered = structuresDiscovered;
++  }
++
++  public Collection<FTSData> getDiscoveredStructuresSet()
++  {
++    return discoveredStructuresSet;
++  }
++
++  @Override
++  protected void txt_search_ActionPerformed()
++  {
++    new Thread()
++    {
++      @Override
++      public void run()
++      {
++        errorWarning.setLength(0);
++        isValidPBDEntry = false;
++        if (txt_search.getText().length() > 0)
++        {
++          String searchTerm = txt_search.getText().toLowerCase();
++          searchTerm = searchTerm.split(":")[0];
++          // System.out.println(">>>>> search term : " + searchTerm);
++          List<FTSDataColumnI> wantedFields = new ArrayList<FTSDataColumnI>();
++          FTSRestRequest pdbRequest = new FTSRestRequest();
++          pdbRequest.setAllowEmptySeq(false);
++          pdbRequest.setResponseSize(1);
++          pdbRequest.setFieldToSearchBy("(pdb_id:");
++          pdbRequest.setWantedFields(wantedFields);
++          pdbRequest
++.setSearchTerm(searchTerm + ")");
++          pdbRequest.setAssociatedSequence(selectedSequence);
++          pdbRestCleint = PDBFTSRestClient.getInstance();
++          wantedFields.add(pdbRestCleint.getPrimaryKeyColumn());
++          FTSRestResponse resultList;
++          try
++          {
++            resultList = pdbRestCleint.executeRequest(pdbRequest);
++          } catch (Exception e)
++          {
++            errorWarning.append(e.getMessage());
++            return;
++          } finally
++          {
++            validateSelections();
++          }
++          if (resultList.getSearchSummary() != null
++                  && resultList.getSearchSummary().size() > 0)
++          {
++            isValidPBDEntry = true;
++          }
++        }
++        validateSelections();
++      }
++    }.start();
++  }
++
++  @Override
++  public void tabRefresh()
++  {
++    if (selectedSequences != null)
++    {
++      Thread refreshThread = new Thread(new Runnable()
++      {
++        @Override
++        public void run()
++        {
++          fetchStructuresMetaData();
++          filterResultSet(((FilterOption) cmb_filterOption
++                  .getSelectedItem()).getValue());
++        }
++      });
++      refreshThread.start();
++    }
++  }
++
++  public class PDBEntryTableModel extends AbstractTableModel
++  {
++    String[] columns = { "Ref Sequence", "PDB Id", "Chain", "Type", "File" };
++
++    private List<CachedPDB> pdbEntries;
++
++    public PDBEntryTableModel(List<CachedPDB> pdbEntries)
++    {
++      this.pdbEntries = new ArrayList<CachedPDB>(pdbEntries);
++    }
++
++    @Override
++    public String getColumnName(int columnIndex)
++    {
++      return columns[columnIndex];
++    }
++
++    @Override
++    public int getRowCount()
++    {
++      return pdbEntries.size();
++    }
++
++    @Override
++    public int getColumnCount()
++    {
++      return columns.length;
++    }
++
++    @Override
++    public boolean isCellEditable(int row, int column)
++    {
++      return false;
++    }
++
++    @Override
++    public Object getValueAt(int rowIndex, int columnIndex)
++    {
++      Object value = "??";
++      CachedPDB entry = pdbEntries.get(rowIndex);
++      switch (columnIndex)
++      {
++      case 0:
++        value = entry.getSequence();
++        break;
++      case 1:
++        value = entry.getPdbEntry();
++        break;
++      case 2:
++        value = entry.getPdbEntry().getChainCode() == null ? "_" : entry
++                .getPdbEntry().getChainCode();
++        break;
++      case 3:
++        value = entry.getPdbEntry().getType();
++        break;
++      case 4:
++        value = entry.getPdbEntry().getFile();
++        break;
++      }
++      return value;
++    }
++
++    @Override
++    public Class<?> getColumnClass(int columnIndex)
++    {
++      return columnIndex == 0 ? SequenceI.class : PDBEntry.class;
++    }
++
++    public CachedPDB getPDBEntryAt(int row)
++    {
++      return pdbEntries.get(row);
++    }
++
++  }
++
++  private class CachedPDB
++  {
++    private SequenceI sequence;
++
++    private PDBEntry pdbEntry;
++
++    public CachedPDB(SequenceI sequence, PDBEntry pdbEntry)
++    {
++      this.sequence = sequence;
++      this.pdbEntry = pdbEntry;
++    }
++
++    public SequenceI getSequence()
++    {
++      return sequence;
++    }
++
++    public PDBEntry getPdbEntry()
++    {
++      return pdbEntry;
++    }
++
++  }
++
++  private IProgressIndicator progressBar;
++
++  @Override
++  public void setProgressBar(String message, long id)
++  {
++    progressBar.setProgressBar(message, id);
++  }
++
++  @Override
++  public void registerHandler(long id, IProgressIndicatorHandler handler)
++  {
++    progressBar.registerHandler(id, handler);
++  }
++
++  @Override
++  public boolean operationInProgress()
++  {
++    return progressBar.operationInProgress();
++  }
++}
index 0000000,0000000..90c74be
new file mode 100755 (executable)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,1080 @@@
++/*
++ * 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.gui;
++
++import jalview.analysis.Conservation;
++import jalview.analysis.NJTree;
++import jalview.api.AlignViewportI;
++import jalview.datamodel.Sequence;
++import jalview.datamodel.SequenceGroup;
++import jalview.datamodel.SequenceI;
++import jalview.datamodel.SequenceNode;
++import jalview.schemes.ColourSchemeI;
++import jalview.schemes.ColourSchemeProperty;
++import jalview.schemes.ResidueProperties;
++import jalview.schemes.UserColourScheme;
++import jalview.structure.SelectionSource;
++import jalview.util.Format;
++import jalview.util.MappingUtils;
++import jalview.util.MessageManager;
++
++import java.awt.Color;
++import java.awt.Dimension;
++import java.awt.Font;
++import java.awt.FontMetrics;
++import java.awt.Graphics;
++import java.awt.Graphics2D;
++import java.awt.Point;
++import java.awt.Rectangle;
++import java.awt.RenderingHints;
++import java.awt.event.MouseEvent;
++import java.awt.event.MouseListener;
++import java.awt.event.MouseMotionListener;
++import java.awt.print.PageFormat;
++import java.awt.print.Printable;
++import java.awt.print.PrinterException;
++import java.awt.print.PrinterJob;
++import java.util.Enumeration;
++import java.util.Hashtable;
++import java.util.Vector;
++
++import javax.swing.JColorChooser;
++import javax.swing.JPanel;
++import javax.swing.JScrollPane;
++import javax.swing.SwingUtilities;
++import javax.swing.ToolTipManager;
++
++/**
++ * DOCUMENT ME!
++ * 
++ * @author $author$
++ * @version $Revision$
++ */
++public class TreeCanvas extends JPanel implements MouseListener, Runnable,
++        Printable, MouseMotionListener, SelectionSource
++{
++  /** DOCUMENT ME!! */
++  public static final String PLACEHOLDER = " * ";
++
++  NJTree tree;
++
++  JScrollPane scrollPane;
++
++  TreePanel tp;
++
++  AlignViewport av;
++
++  AlignmentPanel ap;
++
++  Font font;
++
++  FontMetrics fm;
++
++  boolean fitToWindow = true;
++
++  boolean showDistances = false;
++
++  boolean showBootstrap = false;
++
++  boolean markPlaceholders = false;
++
++  int offx = 20;
++
++  int offy;
++
++  float threshold;
++
++  String longestName;
++
++  int labelLength = -1;
++
++  Hashtable nameHash = new Hashtable();
++
++  Hashtable nodeHash = new Hashtable();
++
++  SequenceNode highlightNode;
++
++  boolean applyToAllViews = false;
++
++  /**
++   * Creates a new TreeCanvas object.
++   * 
++   * @param av
++   *          DOCUMENT ME!
++   * @param tree
++   *          DOCUMENT ME!
++   * @param scroller
++   *          DOCUMENT ME!
++   * @param label
++   *          DOCUMENT ME!
++   */
++  public TreeCanvas(TreePanel tp, AlignmentPanel ap, JScrollPane scroller)
++  {
++    this.tp = tp;
++    this.av = ap.av;
++    this.ap = ap;
++    font = av.getFont();
++    scrollPane = scroller;
++    addMouseListener(this);
++    addMouseMotionListener(this);
++    ToolTipManager.sharedInstance().registerComponent(this);
++  }
++
++  /**
++   * DOCUMENT ME!
++   * 
++   * @param sequence
++   *          DOCUMENT ME!
++   */
++  public void treeSelectionChanged(SequenceI sequence)
++  {
++    AlignmentPanel[] aps = getAssociatedPanels();
++
++    for (int a = 0; a < aps.length; a++)
++    {
++      SequenceGroup selected = aps[a].av.getSelectionGroup();
++
++      if (selected == null)
++      {
++        selected = new SequenceGroup();
++        aps[a].av.setSelectionGroup(selected);
++      }
++
++      selected.setEndRes(aps[a].av.getAlignment().getWidth() - 1);
++      selected.addOrRemove(sequence, true);
++    }
++  }
++
++  /**
++   * DOCUMENT ME!
++   * 
++   * @param tree
++   *          DOCUMENT ME!
++   */
++  public void setTree(NJTree tree)
++  {
++    this.tree = tree;
++    tree.findHeight(tree.getTopNode());
++
++    // Now have to calculate longest name based on the leaves
++    Vector leaves = tree.findLeaves(tree.getTopNode(), new Vector());
++    boolean has_placeholders = false;
++    longestName = "";
++
++    for (int i = 0; i < leaves.size(); i++)
++    {
++      SequenceNode lf = (SequenceNode) leaves.elementAt(i);
++
++      if (lf.isPlaceholder())
++      {
++        has_placeholders = true;
++      }
++
++      if (longestName.length() < ((Sequence) lf.element()).getName()
++              .length())
++      {
++        longestName = TreeCanvas.PLACEHOLDER
++                + ((Sequence) lf.element()).getName();
++      }
++    }
++
++    setMarkPlaceholders(has_placeholders);
++  }
++
++  /**
++   * DOCUMENT ME!
++   * 
++   * @param g
++   *          DOCUMENT ME!
++   * @param node
++   *          DOCUMENT ME!
++   * @param chunk
++   *          DOCUMENT ME!
++   * @param scale
++   *          DOCUMENT ME!
++   * @param width
++   *          DOCUMENT ME!
++   * @param offx
++   *          DOCUMENT ME!
++   * @param offy
++   *          DOCUMENT ME!
++   */
++  public void drawNode(Graphics g, SequenceNode node, float chunk,
++          float scale, int width, int offx, int offy)
++  {
++    if (node == null)
++    {
++      return;
++    }
++
++    if ((node.left() == null) && (node.right() == null))
++    {
++      // Drawing leaf node
++      float height = node.height;
++      float dist = node.dist;
++
++      int xstart = (int) ((height - dist) * scale) + offx;
++      int xend = (int) (height * scale) + offx;
++
++      int ypos = (int) (node.ycount * chunk) + offy;
++
++      if (node.element() instanceof SequenceI)
++      {
++        SequenceI seq = (SequenceI) node.element();
++
++        if (av.getSequenceColour(seq) == Color.white)
++        {
++          g.setColor(Color.black);
++        }
++        else
++        {
++          g.setColor(av.getSequenceColour(seq).darker());
++        }
++      }
++      else
++      {
++        g.setColor(Color.black);
++      }
++
++      // Draw horizontal line
++      g.drawLine(xstart, ypos, xend, ypos);
++
++      String nodeLabel = "";
++
++      if (showDistances && (node.dist > 0))
++      {
++        nodeLabel = new Format("%-.2f").form(node.dist);
++      }
++
++      if (showBootstrap && node.bootstrap > -1)
++      {
++        if (showDistances)
++        {
++          nodeLabel = nodeLabel + " : ";
++        }
++
++        nodeLabel = nodeLabel + String.valueOf(node.bootstrap);
++      }
++
++      if (!nodeLabel.equals(""))
++      {
++        g.drawString(nodeLabel, xstart + 2, ypos - 2);
++      }
++
++      String name = (markPlaceholders && node.isPlaceholder()) ? (PLACEHOLDER + node
++              .getName()) : node.getName();
++
++      int charWidth = fm.stringWidth(name) + 3;
++      int charHeight = font.getSize();
++
++      Rectangle rect = new Rectangle(xend + 10, ypos - charHeight / 2,
++              charWidth, charHeight);
++
++      nameHash.put(node.element(), rect);
++
++      // Colour selected leaves differently
++      SequenceGroup selected = av.getSelectionGroup();
++
++      if ((selected != null)
++              && selected.getSequences(null).contains(node.element()))
++      {
++        g.setColor(Color.gray);
++
++        g.fillRect(xend + 10, ypos - charHeight / 2, charWidth, charHeight);
++        g.setColor(Color.white);
++      }
++
++      g.drawString(name, xend + 10, ypos + fm.getDescent());
++      g.setColor(Color.black);
++    }
++    else
++    {
++      drawNode(g, (SequenceNode) node.left(), chunk, scale, width, offx,
++              offy);
++      drawNode(g, (SequenceNode) node.right(), chunk, scale, width, offx,
++              offy);
++
++      float height = node.height;
++      float dist = node.dist;
++
++      int xstart = (int) ((height - dist) * scale) + offx;
++      int xend = (int) (height * scale) + offx;
++      int ypos = (int) (node.ycount * chunk) + offy;
++
++      g.setColor(node.color.darker());
++
++      // Draw horizontal line
++      g.drawLine(xstart, ypos, xend, ypos);
++      if (node == highlightNode)
++      {
++        g.fillRect(xend - 3, ypos - 3, 6, 6);
++      }
++      else
++      {
++        g.fillRect(xend - 2, ypos - 2, 4, 4);
++      }
++
++      int ystart = (int) (((SequenceNode) node.left()).ycount * chunk)
++              + offy;
++      int yend = (int) (((SequenceNode) node.right()).ycount * chunk)
++              + offy;
++
++      Rectangle pos = new Rectangle(xend - 2, ypos - 2, 5, 5);
++      nodeHash.put(node, pos);
++
++      g.drawLine((int) (height * scale) + offx, ystart,
++              (int) (height * scale) + offx, yend);
++
++      String nodeLabel = "";
++
++      if (showDistances && (node.dist > 0))
++      {
++        nodeLabel = new Format("%-.2f").form(node.dist);
++      }
++
++      if (showBootstrap && node.bootstrap > -1)
++      {
++        if (showDistances)
++        {
++          nodeLabel = nodeLabel + " : ";
++        }
++
++        nodeLabel = nodeLabel + String.valueOf(node.bootstrap);
++      }
++
++      if (!nodeLabel.equals(""))
++      {
++        g.drawString(nodeLabel, xstart + 2, ypos - 2);
++      }
++    }
++  }
++
++  /**
++   * DOCUMENT ME!
++   * 
++   * @param x
++   *          DOCUMENT ME!
++   * @param y
++   *          DOCUMENT ME!
++   * 
++   * @return DOCUMENT ME!
++   */
++  public Object findElement(int x, int y)
++  {
++    Enumeration keys = nameHash.keys();
++
++    while (keys.hasMoreElements())
++    {
++      Object ob = keys.nextElement();
++      Rectangle rect = (Rectangle) nameHash.get(ob);
++
++      if ((x >= rect.x) && (x <= (rect.x + rect.width)) && (y >= rect.y)
++              && (y <= (rect.y + rect.height)))
++      {
++        return ob;
++      }
++    }
++
++    keys = nodeHash.keys();
++
++    while (keys.hasMoreElements())
++    {
++      Object ob = keys.nextElement();
++      Rectangle rect = (Rectangle) nodeHash.get(ob);
++
++      if ((x >= rect.x) && (x <= (rect.x + rect.width)) && (y >= rect.y)
++              && (y <= (rect.y + rect.height)))
++      {
++        return ob;
++      }
++    }
++
++    return null;
++  }
++
++  /**
++   * DOCUMENT ME!
++   * 
++   * @param pickBox
++   *          DOCUMENT ME!
++   */
++  public void pickNodes(Rectangle pickBox)
++  {
++    int width = getWidth();
++    int height = getHeight();
++
++    SequenceNode top = tree.getTopNode();
++
++    float wscale = (float) ((width * .8) - (offx * 2))
++            / tree.getMaxHeight();
++
++    if (top.count == 0)
++    {
++      top.count = ((SequenceNode) top.left()).count
++              + ((SequenceNode) top.right()).count;
++    }
++
++    float chunk = (float) (height - (offy)) / top.count;
++
++    pickNode(pickBox, top, chunk, wscale, width, offx, offy);
++  }
++
++  /**
++   * DOCUMENT ME!
++   * 
++   * @param pickBox
++   *          DOCUMENT ME!
++   * @param node
++   *          DOCUMENT ME!
++   * @param chunk
++   *          DOCUMENT ME!
++   * @param scale
++   *          DOCUMENT ME!
++   * @param width
++   *          DOCUMENT ME!
++   * @param offx
++   *          DOCUMENT ME!
++   * @param offy
++   *          DOCUMENT ME!
++   */
++  public void pickNode(Rectangle pickBox, SequenceNode node, float chunk,
++          float scale, int width, int offx, int offy)
++  {
++    if (node == null)
++    {
++      return;
++    }
++
++    if ((node.left() == null) && (node.right() == null))
++    {
++      float height = node.height;
++      float dist = node.dist;
++
++      int xstart = (int) ((height - dist) * scale) + offx;
++      int xend = (int) (height * scale) + offx;
++
++      int ypos = (int) (node.ycount * chunk) + offy;
++
++      if (pickBox.contains(new Point(xend, ypos)))
++      {
++        if (node.element() instanceof SequenceI)
++        {
++          SequenceI seq = (SequenceI) node.element();
++          SequenceGroup sg = av.getSelectionGroup();
++
++          if (sg != null)
++          {
++            sg.addOrRemove(seq, true);
++          }
++        }
++      }
++    }
++    else
++    {
++      pickNode(pickBox, (SequenceNode) node.left(), chunk, scale, width,
++              offx, offy);
++      pickNode(pickBox, (SequenceNode) node.right(), chunk, scale, width,
++              offx, offy);
++    }
++  }
++
++  /**
++   * DOCUMENT ME!
++   * 
++   * @param node
++   *          DOCUMENT ME!
++   * @param c
++   *          DOCUMENT ME!
++   */
++  public void setColor(SequenceNode node, Color c)
++  {
++    if (node == null)
++    {
++      return;
++    }
++
++    if ((node.left() == null) && (node.right() == null)) // TODO: internal node
++    {
++      node.color = c;
++
++      if (node.element() instanceof SequenceI)
++      {
++        AlignmentPanel[] aps = getAssociatedPanels();
++        if (aps != null)
++        {
++          for (int a = 0; a < aps.length; a++)
++          {
++            final SequenceI seq = (SequenceI) node.element();
++            aps[a].av.setSequenceColour(seq, c);
++          }
++        }
++      }
++    }
++    else
++    {
++      node.color = c;
++      setColor((SequenceNode) node.left(), c);
++      setColor((SequenceNode) node.right(), c);
++    }
++  }
++
++  /**
++   * DOCUMENT ME!
++   */
++  void startPrinting()
++  {
++    Thread thread = new Thread(this);
++    thread.start();
++  }
++
++  // put printing in a thread to avoid painting problems
++  @Override
++  public void run()
++  {
++    PrinterJob printJob = PrinterJob.getPrinterJob();
++    PageFormat pf = printJob.pageDialog(printJob.defaultPage());
++
++    printJob.setPrintable(this, pf);
++
++    if (printJob.printDialog())
++    {
++      try
++      {
++        printJob.print();
++      } catch (Exception PrintException)
++      {
++        PrintException.printStackTrace();
++      }
++    }
++  }
++
++  /**
++   * DOCUMENT ME!
++   * 
++   * @param pg
++   *          DOCUMENT ME!
++   * @param pf
++   *          DOCUMENT ME!
++   * @param pi
++   *          DOCUMENT ME!
++   * 
++   * @return DOCUMENT ME!
++   * 
++   * @throws PrinterException
++   *           DOCUMENT ME!
++   */
++  @Override
++  public int print(Graphics pg, PageFormat pf, int pi)
++          throws PrinterException
++  {
++    pg.setFont(font);
++    pg.translate((int) pf.getImageableX(), (int) pf.getImageableY());
++
++    int pwidth = (int) pf.getImageableWidth();
++    int pheight = (int) pf.getImageableHeight();
++
++    int noPages = getHeight() / pheight;
++
++    if (pi > noPages)
++    {
++      return Printable.NO_SUCH_PAGE;
++    }
++
++    if (pwidth > getWidth())
++    {
++      pwidth = getWidth();
++    }
++
++    if (fitToWindow)
++    {
++      if (pheight > getHeight())
++      {
++        pheight = getHeight();
++      }
++
++      noPages = 0;
++    }
++    else
++    {
++      FontMetrics fm = pg.getFontMetrics(font);
++      int height = fm.getHeight() * nameHash.size();
++      pg.translate(0, -pi * pheight);
++      pg.setClip(0, pi * pheight, pwidth, (pi * pheight) + pheight);
++
++      // translate number of pages,
++      // height is screen size as this is the
++      // non overlapping text size
++      pheight = height;
++    }
++
++    draw(pg, pwidth, pheight);
++
++    return Printable.PAGE_EXISTS;
++  }
++
++  /**
++   * DOCUMENT ME!
++   * 
++   * @param g
++   *          DOCUMENT ME!
++   */
++  @Override
++  public void paintComponent(Graphics g)
++  {
++    super.paintComponent(g);
++    g.setFont(font);
++
++    if (tree == null)
++    {
++      g.drawString(MessageManager.getString("label.calculating_tree")
++              + "....", 20, getHeight() / 2);
++    }
++    else
++    {
++      fm = g.getFontMetrics(font);
++
++      if (nameHash.size() == 0)
++      {
++        repaint();
++      }
++
++      if (fitToWindow
++              || (!fitToWindow && (scrollPane.getHeight() > ((fm
++                      .getHeight() * nameHash.size()) + offy))))
++      {
++        draw(g, scrollPane.getWidth(), scrollPane.getHeight());
++        setPreferredSize(null);
++      }
++      else
++      {
++        setPreferredSize(new Dimension(scrollPane.getWidth(),
++                fm.getHeight() * nameHash.size()));
++        draw(g, scrollPane.getWidth(), fm.getHeight() * nameHash.size());
++      }
++
++      scrollPane.revalidate();
++    }
++  }
++
++  /**
++   * DOCUMENT ME!
++   * 
++   * @param fontSize
++   *          DOCUMENT ME!
++   */
++  @Override
++  public void setFont(Font font)
++  {
++    this.font = font;
++    repaint();
++  }
++
++  /**
++   * DOCUMENT ME!
++   * 
++   * @param g1
++   *          DOCUMENT ME!
++   * @param width
++   *          DOCUMENT ME!
++   * @param height
++   *          DOCUMENT ME!
++   */
++  public void draw(Graphics g1, int width, int height)
++  {
++    Graphics2D g2 = (Graphics2D) g1;
++    g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
++            RenderingHints.VALUE_ANTIALIAS_ON);
++    g2.setColor(Color.white);
++    g2.fillRect(0, 0, width, height);
++    g2.setFont(font);
++
++    if (longestName == null || tree == null)
++    {
++      g2.drawString("Calculating tree.", 20, 20);
++    }
++    offy = font.getSize() + 10;
++
++    fm = g2.getFontMetrics(font);
++
++    labelLength = fm.stringWidth(longestName) + 20; // 20 allows for scrollbar
++
++    float wscale = (width - labelLength - (offx * 2)) / tree.getMaxHeight();
++
++    SequenceNode top = tree.getTopNode();
++
++    if (top.count == 0)
++    {
++      top.count = ((SequenceNode) top.left()).count
++              + ((SequenceNode) top.right()).count;
++    }
++
++    float chunk = (float) (height - (offy)) / top.count;
++
++    drawNode(g2, tree.getTopNode(), chunk, wscale, width, offx, offy);
++
++    if (threshold != 0)
++    {
++      if (av.getCurrentTree() == tree)
++      {
++        g2.setColor(Color.red);
++      }
++      else
++      {
++        g2.setColor(Color.gray);
++      }
++
++      int x = (int) ((threshold * (getWidth() - labelLength - (2 * offx))) + offx);
++
++      g2.drawLine(x, 0, x, getHeight());
++    }
++  }
++
++  /**
++   * DOCUMENT ME!
++   * 
++   * @param e
++   *          DOCUMENT ME!
++   */
++  @Override
++  public void mouseReleased(MouseEvent e)
++  {
++  }
++
++  /**
++   * DOCUMENT ME!
++   * 
++   * @param e
++   *          DOCUMENT ME!
++   */
++  @Override
++  public void mouseEntered(MouseEvent e)
++  {
++  }
++
++  /**
++   * DOCUMENT ME!
++   * 
++   * @param e
++   *          DOCUMENT ME!
++   */
++  @Override
++  public void mouseExited(MouseEvent e)
++  {
++  }
++
++  /**
++   * DOCUMENT ME!
++   * 
++   * @param e
++   *          DOCUMENT ME!
++   */
++  @Override
++  public void mouseClicked(MouseEvent evt)
++  {
++    if (highlightNode != null)
++    {
++      if (SwingUtilities.isRightMouseButton(evt))
++      {
++        Color col = JColorChooser.showDialog(this,
++                MessageManager.getString("label.select_subtree_colour"),
++                highlightNode.color);
++        if (col != null)
++        {
++          setColor(highlightNode, col);
++        }
++      }
++      else if (evt.getClickCount() > 1)
++      {
++        tree.swapNodes(highlightNode);
++        tree.reCount(tree.getTopNode());
++        tree.findHeight(tree.getTopNode());
++      }
++      else
++      {
++        Vector leaves = new Vector();
++        tree.findLeaves(highlightNode, leaves);
++
++        for (int i = 0; i < leaves.size(); i++)
++        {
++          SequenceI seq = (SequenceI) ((SequenceNode) leaves.elementAt(i))
++                  .element();
++          treeSelectionChanged(seq);
++        }
++        av.sendSelection();
++      }
++
++      PaintRefresher.Refresh(tp, av.getSequenceSetId());
++      repaint();
++    }
++  }
++
++  @Override
++  public void mouseMoved(MouseEvent evt)
++  {
++    av.setCurrentTree(tree);
++
++    Object ob = findElement(evt.getX(), evt.getY());
++
++    if (ob instanceof SequenceNode)
++    {
++      highlightNode = (SequenceNode) ob;
++      this.setToolTipText("<html>"
++              + MessageManager.getString("label.highlightnode"));
++      repaint();
++
++    }
++    else
++    {
++      if (highlightNode != null)
++      {
++        highlightNode = null;
++        setToolTipText(null);
++        repaint();
++      }
++    }
++  }
++
++  @Override
++  public void mouseDragged(MouseEvent ect)
++  {
++  }
++
++  /**
++   * DOCUMENT ME!
++   * 
++   * @param e
++   *          DOCUMENT ME!
++   */
++  @Override
++  public void mousePressed(MouseEvent e)
++  {
++    av.setCurrentTree(tree);
++
++    int x = e.getX();
++    int y = e.getY();
++
++    Object ob = findElement(x, y);
++
++    if (ob instanceof SequenceI)
++    {
++      treeSelectionChanged((Sequence) ob);
++      PaintRefresher.Refresh(tp, ap.av.getSequenceSetId());
++      repaint();
++      av.sendSelection();
++      return;
++    }
++    else if (!(ob instanceof SequenceNode))
++    {
++      // Find threshold
++      if (tree.getMaxHeight() != 0)
++      {
++        threshold = (float) (x - offx)
++                / (float) (getWidth() - labelLength - (2 * offx));
++
++        tree.getGroups().removeAllElements();
++        tree.groupNodes(tree.getTopNode(), threshold);
++        setColor(tree.getTopNode(), Color.black);
++
++        AlignmentPanel[] aps = getAssociatedPanels();
++
++        // TODO push calls below into a single AlignViewportI method?
++        // see also AlignViewController.deleteGroups
++        for (int a = 0; a < aps.length; a++)
++        {
++          aps[a].av.setSelectionGroup(null);
++          aps[a].av.getAlignment().deleteAllGroups();
++          aps[a].av.clearSequenceColours();
++          if (aps[a].av.getCodingComplement() != null)
++          {
++            aps[a].av.getCodingComplement().setSelectionGroup(null);
++            aps[a].av.getCodingComplement().getAlignment()
++                    .deleteAllGroups();
++            aps[a].av.getCodingComplement().clearSequenceColours();
++          }
++        }
++        colourGroups();
++      }
++
++      PaintRefresher.Refresh(tp, ap.av.getSequenceSetId());
++      repaint();
++    }
++
++  }
++
++  void colourGroups()
++  {
++    AlignmentPanel[] aps = getAssociatedPanels();
++    for (int i = 0; i < tree.getGroups().size(); i++)
++    {
++      Color col = new Color((int) (Math.random() * 255),
++              (int) (Math.random() * 255), (int) (Math.random() * 255));
++      setColor((SequenceNode) tree.getGroups().elementAt(i), col.brighter());
++
++      Vector l = tree.findLeaves(
++              (SequenceNode) tree.getGroups().elementAt(i), new Vector());
++
++      Vector sequences = new Vector();
++
++      for (int j = 0; j < l.size(); j++)
++      {
++        SequenceI s1 = (SequenceI) ((SequenceNode) l.elementAt(j))
++                .element();
++
++        if (!sequences.contains(s1))
++        {
++          sequences.addElement(s1);
++        }
++      }
++
++      ColourSchemeI cs = null;
++      SequenceGroup sg = new SequenceGroup(sequences, null, cs, true, true,
++              false, 0, av.getAlignment().getWidth() - 1);
++
++      if (av.getGlobalColourScheme() != null)
++      {
++        if (av.getGlobalColourScheme() instanceof UserColourScheme)
++        {
++          cs = new UserColourScheme(
++                  ((UserColourScheme) av.getGlobalColourScheme())
++                          .getColours());
++
++        }
++        else
++        {
++          cs = ColourSchemeProperty.getColour(sg, ColourSchemeProperty
++                  .getColourName(av.getGlobalColourScheme()));
++        }
++        // cs is null if shading is an annotationColourGradient
++        if (cs != null)
++        {
++          cs.setThreshold(av.getGlobalColourScheme().getThreshold(),
++                  av.isIgnoreGapsConsensus());
++        }
++      }
++      sg.cs = cs;
++      // sg.recalcConservation();
++      sg.setName("JTreeGroup:" + sg.hashCode());
++      sg.setIdColour(col);
++
++      for (int a = 0; a < aps.length; a++)
++      {
++        if (aps[a].av.getGlobalColourScheme() != null
++                && aps[a].av.getGlobalColourScheme().conservationApplied())
++        {
++          Conservation c = new Conservation("Group",
++                  ResidueProperties.propHash, 3, sg.getSequences(null),
++                  sg.getStartRes(), sg.getEndRes());
++
++          c.calculate();
++          c.verdict(false, aps[a].av.getConsPercGaps());
++          sg.cs.setConservation(c);
++        }
++
++        aps[a].av.getAlignment().addGroup(new SequenceGroup(sg));
++        // TODO can we push all of the below into AlignViewportI?
++        final AlignViewportI codingComplement = aps[a].av
++                .getCodingComplement();
++        if (codingComplement != null)
++        {
++          if (codingComplement != null)
++          {
++            SequenceGroup mappedGroup = MappingUtils.mapSequenceGroup(sg,
++                    av, codingComplement);
++            if (mappedGroup.getSequences().size() > 0)
++            {
++              codingComplement.getAlignment().addGroup(mappedGroup);
++              for (SequenceI seq : mappedGroup.getSequences())
++              {
++                codingComplement.setSequenceColour(seq, col.brighter());
++              }
++            }
++          }
++        }
++      }
++    }
++
++    // notify the panel(s) to redo any group specific stuff.
++    for (int a = 0; a < aps.length; a++)
++    {
++      aps[a].updateAnnotation();
++      // TODO: JAL-868 - need to ensure view colour change message is broadcast
++      // to any Jmols listening in
++      final AlignViewportI codingComplement = aps[a].av
++              .getCodingComplement();
++      if (codingComplement != null)
++      {
++        ((AlignViewport) codingComplement).getAlignPanel()
++                .updateAnnotation();
++
++      }
++
++    }
++
++  }
++
++  /**
++   * DOCUMENT ME!
++   * 
++   * @param state
++   *          DOCUMENT ME!
++   */
++  public void setShowDistances(boolean state)
++  {
++    this.showDistances = state;
++    repaint();
++  }
++
++  /**
++   * DOCUMENT ME!
++   * 
++   * @param state
++   *          DOCUMENT ME!
++   */
++  public void setShowBootstrap(boolean state)
++  {
++    this.showBootstrap = state;
++    repaint();
++  }
++
++  /**
++   * DOCUMENT ME!
++   * 
++   * @param state
++   *          DOCUMENT ME!
++   */
++  public void setMarkPlaceholders(boolean state)
++  {
++    this.markPlaceholders = state;
++    repaint();
++  }
++
++  AlignmentPanel[] getAssociatedPanels()
++  {
++    if (applyToAllViews)
++    {
++      return PaintRefresher.getAssociatedPanels(av.getSequenceSetId());
++    }
++    else
++    {
++      return new AlignmentPanel[] { ap };
++    }
++  }
++}
@@@ -28,9 -28,8 +28,10 @@@ import jalview.api.AlignViewportI
  import jalview.datamodel.AlignmentAnnotation;
  import jalview.datamodel.Annotation;
  import jalview.datamodel.ColumnSelection;
+ import jalview.datamodel.HiddenColumns;
  import jalview.datamodel.ProfilesI;
 +import jalview.renderer.api.AnnotationRendererFactoryI;
 +import jalview.renderer.api.AnnotationRowRendererI;
  import jalview.schemes.ColourSchemeI;
  import jalview.schemes.NucleotideColourScheme;
  import jalview.schemes.ResidueProperties;
@@@ -1045,32 -1044,6 +1049,32 @@@ public class AnnotationRendere
                      row.graphMin, row.graphMax, y, renderHistogram,
                      renderProfile, normaliseProfile);
            }
 +          else
 +          {
 +            AnnotationRowRendererI renderer = rendererFactoryI
 +                    .getRendererFor(row);
 +            if (renderer != null)
 +            {
 +              renderer.renderRow(g, charWidth, charHeight,
-                       hasHiddenColumns, av, columnSelection, row,
-                       row_annotations, startRes, endRes, row.graphMin,
++                      hasHiddenColumns, av, hiddenColumns, columnSelection,
++                      row, row_annotations, startRes, endRes, row.graphMin,
 +                      row.graphMax, y);
 +            }
 +            if (debugRedraw)
 +            {
 +              if (renderer == null)
 +              {
 +                System.err.println("No renderer found for "
 +                        + row.toString());
 +              }
 +              else
 +              {
 +                System.err.println("rendered with "
 +                        + renderer.getClass().toString());
 +              }
 +            }
 +
 +          }
          }
        }
        else
index 35b73a4,0000000..bd59315
mode 100644,000000..100644
--- /dev/null
@@@ -1,119 -1,0 +1,120 @@@
 +/**
 + * 
 + */
 +package jalview.renderer;
 +
 +import jalview.api.AlignViewportI;
 +import jalview.datamodel.AlignmentAnnotation;
 +import jalview.datamodel.Annotation;
 +import jalview.datamodel.ColumnSelection;
 +import jalview.datamodel.ContactListI;
 +import jalview.datamodel.ContactRange;
++import jalview.datamodel.HiddenColumns;
 +import jalview.renderer.api.AnnotationRowRendererI;
 +
 +import java.awt.Color;
 +import java.awt.Graphics;
 +
 +/**
 + * @author jprocter
 + *
 + */
 +public class ContactMapRenderer implements AnnotationRowRendererI
 +{
 +
 +  @Override
 +  public void renderRow(Graphics g, int charWidth, int charHeight,
-           boolean hasHiddenColumns, AlignViewportI viewport,
++          boolean hasHiddenColumns, AlignViewportI viewport, HiddenColumns hiddenColumns,
 +          ColumnSelection columnSelection, AlignmentAnnotation _aa,
 +          Annotation[] aa_annotations, int sRes, int eRes, float min,
 +          float max, int y)
 +  {
 +    if (sRes > aa_annotations.length)
 +    {
 +      return;
 +    }
 +    eRes = Math.min(eRes, aa_annotations.length);
 +
 +    int x = 0, y2 = y;
 +
 +    g.setColor(Color.pink);
 +
 +    g.drawLine(x, y2, (eRes - sRes) * charWidth, y2);
 +
 +    int column;
 +    int aaMax = aa_annotations.length - 1;
 +    while (x < eRes - sRes)
 +    {
 +      column = sRes + x;
 +      if (hasHiddenColumns)
 +      {
-         column = columnSelection.adjustForHiddenColumns(column);
++        column = hiddenColumns.adjustForHiddenColumns(column);
 +      }
 +
 +      if (column > aaMax)
 +      {
 +        break;
 +      }
 +
 +      if (aa_annotations[column] == null)
 +      {
 +        x++;
 +        continue;
 +      }
 +      /*
 +       * {profile type, #values, total count, char1, pct1, char2, pct2...}
 +       */
 +      ContactListI contacts = viewport.getContactList(_aa, column);
 +      min = _aa.graphMin;
 +      max = _aa.graphMax;
 +      if (contacts == null)
 +      {
 +        return;
 +      }
 +
 +      // cell height to render
 +      double scale = (_aa.graphHeight < contacts.getContactHeight()) ? 1
 +              : (((double) _aa.graphHeight) / (double) contacts
 +                      .getContactHeight());
 +      int cstart, cend = -1;
 +      for (int ht = y2, eht = y2 - _aa.graphHeight; ht >= eht; ht -= scale)
 +      {
 +        cstart = cend + 1;
 +        cend = -1
 +                + (contacts.getContactHeight() * (ht - eht) / _aa.graphHeight);
 +        // TODO show maximum colour for range - sort of done
 +        // also need a 'getMaxPosForRange(start,end)'
 +        g.setColor(getColorForRange(contacts, cstart, cend));
 +
 +        if (scale > 1)
 +        {
 +          g.fillRect(x * charWidth, ht, charWidth, 1 + (int) scale);
 +        }
 +        else
 +        {
 +          g.drawLine(x * charWidth, ht, (x + 1) * charWidth, ht);
 +        }
 +      }
 +      x++;
 +    }
 +
 +  }
 +
 +  Color minColor = Color.white, maxColor = Color.magenta;
 +
 +  float min, max;
 +
 +  Color shadeFor(float value)
 +  {
 +    return jalview.util.ColorUtils.getGraduatedColour(value, 0, minColor,
 +            max, maxColor);
 +  }
 +
 +  public Color getColorForRange(ContactListI cl, int i, int j)
 +  {
 +    ContactRange cr = cl.getRangeFor(i, j);
 +    // average for moment - probably more interested in maxIntProj though
 +    return shadeFor((float) cr.getMean());
 +  }
 +
 +}
index 0f0d851,0000000..bedbad5
mode 100644,000000..100644
--- /dev/null
@@@ -1,19 -1,0 +1,21 @@@
 +package jalview.renderer.api;
 +
 +import jalview.api.AlignViewportI;
 +import jalview.datamodel.AlignmentAnnotation;
 +import jalview.datamodel.Annotation;
 +import jalview.datamodel.ColumnSelection;
++import jalview.datamodel.HiddenColumns;
 +
 +import java.awt.Graphics;
 +
 +public interface AnnotationRowRendererI
 +{
 +
 +  void renderRow(Graphics g, int charWidth, int charHeight,
 +          boolean hasHiddenColumns, AlignViewportI av,
++          HiddenColumns hiddenColumns,
 +          ColumnSelection columnSelection, AlignmentAnnotation row,
 +          Annotation[] row_annotations, int startRes, int endRes,
 +          float graphMin, float graphMax, int y);
 +
 +}
index 0000000,0000000..5b06097
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,1327 @@@
++/*
++ * 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.structure;
++
++import jalview.analysis.AlignSeq;
++import jalview.api.StructureSelectionManagerProvider;
++import jalview.commands.CommandI;
++import jalview.commands.EditCommand;
++import jalview.commands.OrderCommand;
++import jalview.datamodel.AlignedCodonFrame;
++import jalview.datamodel.AlignmentAnnotation;
++import jalview.datamodel.AlignmentI;
++import jalview.datamodel.Annotation;
++import jalview.datamodel.PDBEntry;
++import jalview.datamodel.SearchResults;
++import jalview.datamodel.SequenceI;
++import jalview.gui.IProgressIndicator;
++import jalview.io.AppletFormatAdapter;
++import jalview.io.StructureFile;
++import jalview.util.MappingUtils;
++import jalview.util.MessageManager;
++import jalview.ws.sifts.SiftsClient;
++import jalview.ws.sifts.SiftsException;
++import jalview.ws.sifts.SiftsSettings;
++
++import java.io.PrintStream;
++import java.util.ArrayList;
++import java.util.Arrays;
++import java.util.Collections;
++import java.util.Enumeration;
++import java.util.HashMap;
++import java.util.IdentityHashMap;
++import java.util.List;
++import java.util.Map;
++import java.util.Vector;
++
++import MCview.Atom;
++import MCview.PDBChain;
++import MCview.PDBfile;
++
++public class StructureSelectionManager
++{
++  public final static String NEWLINE = System.lineSeparator();
++
++  static IdentityHashMap<StructureSelectionManagerProvider, StructureSelectionManager> instances;
++
++  private List<StructureMapping> mappings = new ArrayList<StructureMapping>();
++
++  private boolean processSecondaryStructure = false;
++
++  private boolean secStructServices = false;
++
++  private boolean addTempFacAnnot = false;
++
++  private SiftsClient siftsClient = null;
++
++  /*
++   * Set of any registered mappings between (dataset) sequences.
++   */
++  private List<AlignedCodonFrame> seqmappings = new ArrayList<AlignedCodonFrame>();
++
++  private List<CommandListener> commandListeners = new ArrayList<CommandListener>();
++
++  private List<SelectionListener> sel_listeners = new ArrayList<SelectionListener>();
++
++  /**
++   * @return true if will try to use external services for processing secondary
++   *         structure
++   */
++  public boolean isSecStructServices()
++  {
++    return secStructServices;
++  }
++
++  /**
++   * control use of external services for processing secondary structure
++   * 
++   * @param secStructServices
++   */
++  public void setSecStructServices(boolean secStructServices)
++  {
++    this.secStructServices = secStructServices;
++  }
++
++  /**
++   * flag controlling addition of any kind of structural annotation
++   * 
++   * @return true if temperature factor annotation will be added
++   */
++  public boolean isAddTempFacAnnot()
++  {
++    return addTempFacAnnot;
++  }
++
++  /**
++   * set flag controlling addition of structural annotation
++   * 
++   * @param addTempFacAnnot
++   */
++  public void setAddTempFacAnnot(boolean addTempFacAnnot)
++  {
++    this.addTempFacAnnot = addTempFacAnnot;
++  }
++
++  /**
++   * 
++   * @return if true, the structure manager will attempt to add secondary
++   *         structure lines for unannotated sequences
++   */
++
++  public boolean isProcessSecondaryStructure()
++  {
++    return processSecondaryStructure;
++  }
++
++  /**
++   * Control whether structure manager will try to annotate mapped sequences
++   * with secondary structure from PDB data.
++   * 
++   * @param enable
++   */
++  public void setProcessSecondaryStructure(boolean enable)
++  {
++    processSecondaryStructure = enable;
++  }
++
++  /**
++   * debug function - write all mappings to stdout
++   */
++  public void reportMapping()
++  {
++    if (mappings.isEmpty())
++    {
++      System.err.println("reportMapping: No PDB/Sequence mappings.");
++    }
++    else
++    {
++      System.err.println("reportMapping: There are " + mappings.size()
++              + " mappings.");
++      int i = 0;
++      for (StructureMapping sm : mappings)
++      {
++        System.err.println("mapping " + i++ + " : " + sm.pdbfile);
++      }
++    }
++  }
++
++  /**
++   * map between the PDB IDs (or structure identifiers) used by Jalview and the
++   * absolute filenames for PDB data that corresponds to it
++   */
++  Map<String, String> pdbIdFileName = new HashMap<String, String>();
++
++  Map<String, String> pdbFileNameId = new HashMap<String, String>();
++
++  public void registerPDBFile(String idForFile, String absoluteFile)
++  {
++    pdbIdFileName.put(idForFile, absoluteFile);
++    pdbFileNameId.put(absoluteFile, idForFile);
++  }
++
++  public String findIdForPDBFile(String idOrFile)
++  {
++    String id = pdbFileNameId.get(idOrFile);
++    return id;
++  }
++
++  public String findFileForPDBId(String idOrFile)
++  {
++    String id = pdbIdFileName.get(idOrFile);
++    return id;
++  }
++
++  public boolean isPDBFileRegistered(String idOrFile)
++  {
++    return pdbFileNameId.containsKey(idOrFile)
++            || pdbIdFileName.containsKey(idOrFile);
++  }
++
++  private static StructureSelectionManager nullProvider = null;
++
++  public static StructureSelectionManager getStructureSelectionManager(
++          StructureSelectionManagerProvider context)
++  {
++    if (context == null)
++    {
++      if (nullProvider == null)
++      {
++        if (instances != null)
++        {
++          throw new Error(
++                  MessageManager
++                          .getString("error.implementation_error_structure_selection_manager_null"),
++                  new NullPointerException(MessageManager
++                          .getString("exception.ssm_context_is_null")));
++        }
++        else
++        {
++          nullProvider = new StructureSelectionManager();
++        }
++        return nullProvider;
++      }
++    }
++    if (instances == null)
++    {
++      instances = new java.util.IdentityHashMap<StructureSelectionManagerProvider, StructureSelectionManager>();
++    }
++    StructureSelectionManager instance = instances.get(context);
++    if (instance == null)
++    {
++      if (nullProvider != null)
++      {
++        instance = nullProvider;
++      }
++      else
++      {
++        instance = new StructureSelectionManager();
++      }
++      instances.put(context, instance);
++    }
++    return instance;
++  }
++
++  /**
++   * flag controlling whether SeqMappings are relayed from received sequence
++   * mouse over events to other sequences
++   */
++  boolean relaySeqMappings = true;
++
++  /**
++   * Enable or disable relay of seqMapping events to other sequences. You might
++   * want to do this if there are many sequence mappings and the host computer
++   * is slow
++   * 
++   * @param relay
++   */
++  public void setRelaySeqMappings(boolean relay)
++  {
++    relaySeqMappings = relay;
++  }
++
++  /**
++   * get the state of the relay seqMappings flag.
++   * 
++   * @return true if sequence mouse overs are being relayed to other mapped
++   *         sequences
++   */
++  public boolean isRelaySeqMappingsEnabled()
++  {
++    return relaySeqMappings;
++  }
++
++  Vector listeners = new Vector();
++
++  /**
++   * register a listener for alignment sequence mouseover events
++   * 
++   * @param svl
++   */
++  public void addStructureViewerListener(Object svl)
++  {
++    if (!listeners.contains(svl))
++    {
++      listeners.addElement(svl);
++    }
++  }
++
++  /**
++   * Returns the file name for a mapped PDB id (or null if not mapped).
++   * 
++   * @param pdbid
++   * @return
++   */
++  public String alreadyMappedToFile(String pdbid)
++  {
++    for (StructureMapping sm : mappings)
++    {
++      if (sm.getPdbId().equals(pdbid))
++      {
++        return sm.pdbfile;
++      }
++    }
++    return null;
++  }
++
++  /**
++   * Import structure data and register a structure mapping for broadcasting
++   * colouring, mouseovers and selection events (convenience wrapper).
++   * 
++   * @param sequence
++   *          - one or more sequences to be mapped to pdbFile
++   * @param targetChains
++   *          - optional chain specification for mapping each sequence to pdb
++   *          (may be nill, individual elements may be nill)
++   * @param pdbFile
++   *          - structure data resource
++   * @param protocol
++   *          - how to resolve data from resource
++   * @return null or the structure data parsed as a pdb file
++   */
++  synchronized public StructureFile setMapping(SequenceI[] sequence,
++          String[] targetChains, String pdbFile, String protocol,
++          IProgressIndicator progress)
++  {
++    return computeMapping(true, sequence, targetChains, pdbFile, protocol,
++            progress);
++  }
++
++
++  /**
++   * create sequence structure mappings between each sequence and the given
++   * pdbFile (retrieved via the given protocol).
++   * 
++   * @param forStructureView
++   *          when true, record the mapping for use in mouseOvers
++   * 
++   * @param sequenceArray
++   *          - one or more sequences to be mapped to pdbFile
++   * @param targetChainIds
++   *          - optional chain specification for mapping each sequence to pdb
++   *          (may be null, individual elements may be null)
++   * @param pdbFile
++   *          - structure data resource
++   * @param protocol
++   *          - how to resolve data from resource
++   * @return null or the structure data parsed as a pdb file
++   */
++  synchronized public StructureFile setMapping(boolean forStructureView,
++          SequenceI[] sequenceArray, String[] targetChainIds,
++          String pdbFile,
++          String protocol)
++  {
++    return computeMapping(forStructureView, sequenceArray, targetChainIds,
++            pdbFile, protocol, null);
++  }
++
++  synchronized public StructureFile computeMapping(
++          boolean forStructureView, SequenceI[] sequenceArray,
++          String[] targetChainIds, String pdbFile, String protocol,
++          IProgressIndicator progress)
++  {
++    long progressSessionId = System.currentTimeMillis() * 3;
++    /*
++     * There will be better ways of doing this in the future, for now we'll use
++     * the tried and tested MCview pdb mapping
++     */
++    boolean parseSecStr = processSecondaryStructure;
++    if (isPDBFileRegistered(pdbFile))
++    {
++      for (SequenceI sq : sequenceArray)
++      {
++        SequenceI ds = sq;
++        while (ds.getDatasetSequence() != null)
++        {
++          ds = ds.getDatasetSequence();
++        }
++        ;
++        if (ds.getAnnotation() != null)
++        {
++          for (AlignmentAnnotation ala : ds.getAnnotation())
++          {
++            // false if any annotation present from this structure
++            // JBPNote this fails for jmol/chimera view because the *file* is
++            // passed, not the structure data ID -
++            if (PDBfile.isCalcIdForFile(ala, findIdForPDBFile(pdbFile)))
++            {
++              parseSecStr = false;
++            }
++          }
++        }
++      }
++    }
++    StructureFile pdb = null;
++    boolean isMapUsingSIFTs = SiftsSettings.isMapWithSifts();
++    try
++    {
++
++      if (pdbFile != null && isCIFFile(pdbFile))
++      {
++        pdb = new jalview.ext.jmol.JmolParser(addTempFacAnnot, parseSecStr,
++                secStructServices, pdbFile, protocol);
++      }
++      else
++      {
++        pdb = new PDBfile(addTempFacAnnot, parseSecStr, secStructServices,
++                pdbFile, protocol);
++      }
++
++      if (pdb.getId() != null && pdb.getId().trim().length() > 0
++              && AppletFormatAdapter.FILE.equals(protocol))
++      {
++        registerPDBFile(pdb.getId().trim(), pdbFile);
++      }
++    } catch (Exception ex)
++    {
++      ex.printStackTrace();
++      return null;
++    }
++
++    try
++    {
++      if (isMapUsingSIFTs)
++      {
++        siftsClient = new SiftsClient(pdb);
++      }
++    } catch (SiftsException e)
++    {
++      isMapUsingSIFTs = false;
++      e.printStackTrace();
++    }
++
++    String targetChainId;
++    for (int s = 0; s < sequenceArray.length; s++)
++    {
++      boolean infChain = true;
++      final SequenceI seq = sequenceArray[s];
++      if (targetChainIds != null && targetChainIds[s] != null)
++      {
++        infChain = false;
++        targetChainId = targetChainIds[s];
++      }
++      else if (seq.getName().indexOf("|") > -1)
++      {
++        targetChainId = seq.getName().substring(
++                seq.getName().lastIndexOf("|") + 1);
++        if (targetChainId.length() > 1)
++        {
++          if (targetChainId.trim().length() == 0)
++          {
++            targetChainId = " ";
++          }
++          else
++          {
++            // not a valid chain identifier
++            targetChainId = "";
++          }
++        }
++      }
++      else
++      {
++        targetChainId = "";
++      }
++
++      /*
++       * Attempt pairwise alignment of the sequence with each chain in the PDB,
++       * and remember the highest scoring chain
++       */
++      int max = -10;
++      AlignSeq maxAlignseq = null;
++      String maxChainId = " ";
++      PDBChain maxChain = null;
++      boolean first = true;
++      for (PDBChain chain : pdb.getChains())
++      {
++        if (targetChainId.length() > 0 && !targetChainId.equals(chain.id)
++                && !infChain)
++        {
++          continue; // don't try to map chains don't match.
++        }
++        // TODO: correctly determine sequence type for mixed na/peptide
++        // structures
++        final String type = chain.isNa ? AlignSeq.DNA : AlignSeq.PEP;
++        AlignSeq as = AlignSeq.doGlobalNWAlignment(seq, chain.sequence,
++                type);
++        // equivalent to:
++        // AlignSeq as = new AlignSeq(sequence[s], chain.sequence, type);
++        // as.calcScoreMatrix();
++        // as.traceAlignment();
++
++        if (first || as.maxscore > max
++                || (as.maxscore == max && chain.id.equals(targetChainId)))
++        {
++          first = false;
++          maxChain = chain;
++          max = as.maxscore;
++          maxAlignseq = as;
++          maxChainId = chain.id;
++        }
++      }
++      if (maxChain == null)
++      {
++        continue;
++      }
++
++      if (protocol.equals(jalview.io.AppletFormatAdapter.PASTE))
++      {
++        pdbFile = "INLINE" + pdb.getId();
++      }
++      ArrayList<StructureMapping> seqToStrucMapping = new ArrayList<StructureMapping>();
++      if (isMapUsingSIFTs)
++      {
++<<<<<<< Updated upstream
++        setProgressBar(null);
++        setProgressBar(MessageManager
++                .getString("status.obtaining_mapping_with_sifts"));
++=======
++        if (progress!=null) {
++          progress.setProgressBar("Obtaining mapping with SIFTS",
++                  progressSessionId);
++        }
++>>>>>>> Stashed changes
++        jalview.datamodel.Mapping sqmpping = maxAlignseq
++                .getMappingFromS1(false);
++        if (targetChainId != null && !targetChainId.trim().isEmpty())
++        {
++          StructureMapping siftsMapping;
++          try
++          {
++            siftsMapping = getStructureMapping(seq, pdbFile, targetChainId,
++                    pdb, maxChain, sqmpping, maxAlignseq);
++            seqToStrucMapping.add(siftsMapping);
++            maxChain.makeExactMapping(maxAlignseq, seq);
++            maxChain.transferRESNUMFeatures(seq, null);
++            maxChain.transferResidueAnnotation(siftsMapping, sqmpping);
++          } catch (SiftsException e)
++          {
++            // fall back to NW alignment
++            System.err.println(e.getMessage());
++            StructureMapping nwMapping = getNWMappings(seq, pdbFile,
++                    targetChainId, maxChain, pdb, maxAlignseq);
++            seqToStrucMapping.add(nwMapping);
++          }
++        }
++        else
++        {
++          ArrayList<StructureMapping> foundSiftsMappings = new ArrayList<StructureMapping>();
++          for (PDBChain chain : pdb.getChains())
++          {
++            try
++            {
++              StructureMapping siftsMapping = getStructureMapping(seq,
++                      pdbFile,
++                      chain.id, pdb, chain, sqmpping, maxAlignseq);
++              foundSiftsMappings.add(siftsMapping);
++            } catch (SiftsException e)
++            {
++              System.err.println(e.getMessage());
++            }
++          }
++          if (!foundSiftsMappings.isEmpty())
++          {
++            seqToStrucMapping.addAll(foundSiftsMappings);
++            maxChain.makeExactMapping(maxAlignseq, seq);
++            maxChain.transferRESNUMFeatures(seq, null);
++            maxChain.transferResidueAnnotation(foundSiftsMappings.get(0),
++                    sqmpping);
++          }
++          else
++          {
++            StructureMapping nwMapping = getNWMappings(seq, pdbFile,
++                    maxChainId, maxChain, pdb, maxAlignseq);
++            seqToStrucMapping.add(nwMapping);
++          }
++        }
++      }
++      else
++      {
++<<<<<<< Updated upstream
++        setProgressBar(null);
++        setProgressBar(MessageManager
++                .getString("status.obtaining_mapping_with_nw_alignment"));
++=======
++        if (progress != null)
++        {
++          progress.setProgressBar("Obtaining mapping with NW alignment",
++                  progressSessionId);
++        }
++>>>>>>> Stashed changes
++        seqToStrucMapping.add(getNWMappings(seq, pdbFile, maxChainId,
++                maxChain, pdb, maxAlignseq));
++      }
++      if (forStructureView)
++      {
++        mappings.addAll(seqToStrucMapping);
++      }
++      if (progress != null)
++      {
++        progress.setProgressBar(null, progressSessionId);
++      }
++    }
++    return pdb;
++  }
++
++  private boolean isCIFFile(String filename)
++  {
++    String fileExt = filename.substring(filename.lastIndexOf(".") + 1,
++            filename.length());
++    return "cif".equalsIgnoreCase(fileExt);
++  }
++
++  private StructureMapping getStructureMapping(SequenceI seq,
++          String pdbFile, String targetChainId, StructureFile pdb,
++          PDBChain maxChain, jalview.datamodel.Mapping sqmpping,
++          AlignSeq maxAlignseq) throws SiftsException
++  {
++      StructureMapping curChainMapping = siftsClient
++              .getSiftsStructureMapping(seq, pdbFile, targetChainId);
++      try
++      {
++      PDBChain chain = pdb.findChain(targetChainId);
++      if (chain != null)
++      {
++        chain.transferResidueAnnotation(curChainMapping, sqmpping);
++      }
++      } catch (Exception e)
++      {
++        e.printStackTrace();
++      }
++      return curChainMapping;
++  }
++
++  private StructureMapping getNWMappings(SequenceI seq,
++          String pdbFile,
++          String maxChainId, PDBChain maxChain, StructureFile pdb,
++          AlignSeq maxAlignseq)
++  {
++    final StringBuilder mappingDetails = new StringBuilder(128);
++    mappingDetails.append(NEWLINE).append(
++            "Sequence \u27f7 Structure mapping details");
++    mappingDetails.append(NEWLINE);
++    mappingDetails
++            .append("Method: inferred with Needleman & Wunsch alignment");
++    mappingDetails.append(NEWLINE).append("PDB Sequence is :")
++            .append(NEWLINE).append("Sequence = ")
++            .append(maxChain.sequence.getSequenceAsString());
++    mappingDetails.append(NEWLINE).append("No of residues = ")
++            .append(maxChain.residues.size()).append(NEWLINE)
++            .append(NEWLINE);
++    PrintStream ps = new PrintStream(System.out)
++    {
++      @Override
++      public void print(String x)
++      {
++        mappingDetails.append(x);
++      }
++
++      @Override
++      public void println()
++      {
++        mappingDetails.append(NEWLINE);
++      }
++    };
++
++    maxAlignseq.printAlignment(ps);
++
++    mappingDetails.append(NEWLINE).append("PDB start/end ");
++    mappingDetails.append(String.valueOf(maxAlignseq.seq2start))
++            .append(" ");
++    mappingDetails.append(String.valueOf(maxAlignseq.seq2end));
++    mappingDetails.append(NEWLINE).append("SEQ start/end ");
++    mappingDetails.append(
++            String.valueOf(maxAlignseq.seq1start + (seq.getStart() - 1)))
++            .append(" ");
++    mappingDetails.append(String.valueOf(maxAlignseq.seq1end
++            + (seq.getStart() - 1)));
++    mappingDetails.append(NEWLINE);
++    maxChain.makeExactMapping(maxAlignseq, seq);
++    jalview.datamodel.Mapping sqmpping = maxAlignseq
++            .getMappingFromS1(false);
++    maxChain.transferRESNUMFeatures(seq, null);
++
++    HashMap<Integer, int[]> mapping = new HashMap<Integer, int[]>();
++    int resNum = -10000;
++    int index = 0;
++    char insCode = ' ';
++
++    do
++    {
++      Atom tmp = maxChain.atoms.elementAt(index);
++      if ((resNum != tmp.resNumber || insCode != tmp.insCode)
++              && tmp.alignmentMapping != -1)
++      {
++        resNum = tmp.resNumber;
++        insCode = tmp.insCode;
++        if (tmp.alignmentMapping >= -1)
++        {
++          mapping.put(tmp.alignmentMapping + 1, new int[] { tmp.resNumber,
++              tmp.atomIndex });
++        }
++      }
++
++      index++;
++    } while (index < maxChain.atoms.size());
++
++    StructureMapping nwMapping = new StructureMapping(seq, pdbFile,
++            pdb.getId(), maxChainId, mapping, mappingDetails.toString());
++    maxChain.transferResidueAnnotation(nwMapping, sqmpping);
++    return nwMapping;
++  }
++
++  public void removeStructureViewerListener(Object svl, String[] pdbfiles)
++  {
++    listeners.removeElement(svl);
++    if (svl instanceof SequenceListener)
++    {
++      for (int i = 0; i < listeners.size(); i++)
++      {
++        if (listeners.elementAt(i) instanceof StructureListener)
++        {
++          ((StructureListener) listeners.elementAt(i))
++                  .releaseReferences(svl);
++        }
++      }
++    }
++
++    if (pdbfiles == null)
++    {
++      return;
++    }
++
++    /*
++     * Remove mappings to the closed listener's PDB files, but first check if
++     * another listener is still interested
++     */
++    List<String> pdbs = new ArrayList<String>(Arrays.asList(pdbfiles));
++
++    StructureListener sl;
++    for (int i = 0; i < listeners.size(); i++)
++    {
++      if (listeners.elementAt(i) instanceof StructureListener)
++      {
++        sl = (StructureListener) listeners.elementAt(i);
++        for (String pdbfile : sl.getPdbFile())
++        {
++          pdbs.remove(pdbfile);
++        }
++      }
++    }
++
++    /*
++     * Rebuild the mappings set, retaining only those which are for 'other' PDB
++     * files
++     */
++    if (pdbs.size() > 0)
++    {
++      List<StructureMapping> tmp = new ArrayList<StructureMapping>();
++      for (StructureMapping sm : mappings)
++      {
++        if (!pdbs.contains(sm.pdbfile))
++        {
++          tmp.add(sm);
++        }
++      }
++
++      mappings = tmp;
++    }
++  }
++
++  /**
++   * Propagate mouseover of a single position in a structure
++   * 
++   * @param pdbResNum
++   * @param chain
++   * @param pdbfile
++   */
++  public void mouseOverStructure(int pdbResNum, String chain, String pdbfile)
++  {
++    AtomSpec atomSpec = new AtomSpec(pdbfile, chain, pdbResNum, 0);
++    List<AtomSpec> atoms = Collections.singletonList(atomSpec);
++    mouseOverStructure(atoms);
++  }
++
++  /**
++   * Propagate mouseover or selection of multiple positions in a structure
++   * 
++   * @param atoms
++   */
++  public void mouseOverStructure(List<AtomSpec> atoms)
++  {
++    if (listeners == null)
++    {
++      // old or prematurely sent event
++      return;
++    }
++    boolean hasSequenceListener = false;
++    for (int i = 0; i < listeners.size(); i++)
++    {
++      if (listeners.elementAt(i) instanceof SequenceListener)
++      {
++        hasSequenceListener = true;
++      }
++    }
++    if (!hasSequenceListener)
++    {
++      return;
++    }
++
++    SearchResults results = new SearchResults();
++    for (AtomSpec atom : atoms)
++    {
++      SequenceI lastseq = null;
++      int lastipos = -1;
++      for (StructureMapping sm : mappings)
++      {
++        if (sm.pdbfile.equals(atom.getPdbFile())
++                && sm.pdbchain.equals(atom.getChain()))
++        {
++          int indexpos = sm.getSeqPos(atom.getPdbResNum());
++          if (lastipos != indexpos && lastseq != sm.sequence)
++          {
++            results.addResult(sm.sequence, indexpos, indexpos);
++            lastipos = indexpos;
++            lastseq = sm.sequence;
++            // construct highlighted sequence list
++            for (AlignedCodonFrame acf : seqmappings)
++            {
++              acf.markMappedRegion(sm.sequence, indexpos, results);
++            }
++          }
++        }
++      }
++    }
++    for (Object li : listeners)
++    {
++      if (li instanceof SequenceListener)
++      {
++        ((SequenceListener) li).highlightSequence(results);
++      }
++    }
++  }
++
++  /**
++   * highlight regions associated with a position (indexpos) in seq
++   * 
++   * @param seq
++   *          the sequence that the mouse over occurred on
++   * @param indexpos
++   *          the absolute position being mouseovered in seq (0 to seq.length())
++   * @param seqPos
++   *          the sequence position (if -1, seq.findPosition is called to
++   *          resolve the residue number)
++   */
++  public void mouseOverSequence(SequenceI seq, int indexpos, int seqPos,
++          VamsasSource source)
++  {
++    boolean hasSequenceListeners = handlingVamsasMo
++            || !seqmappings.isEmpty();
++    SearchResults results = null;
++    if (seqPos == -1)
++    {
++      seqPos = seq.findPosition(indexpos);
++    }
++    for (int i = 0; i < listeners.size(); i++)
++    {
++      Object listener = listeners.elementAt(i);
++      if (listener == source)
++      {
++        // TODO listener (e.g. SeqPanel) is never == source (AlignViewport)
++        // Temporary fudge with SequenceListener.getVamsasSource()
++        continue;
++      }
++      if (listener instanceof StructureListener)
++      {
++        highlightStructure((StructureListener) listener, seq, seqPos);
++      }
++      else
++      {
++        if (listener instanceof SequenceListener)
++        {
++          final SequenceListener seqListener = (SequenceListener) listener;
++          if (hasSequenceListeners
++                  && seqListener.getVamsasSource() != source)
++          {
++            if (relaySeqMappings)
++            {
++              if (results == null)
++              {
++                results = MappingUtils.buildSearchResults(seq, seqPos,
++                        seqmappings);
++              }
++              if (handlingVamsasMo)
++              {
++                results.addResult(seq, seqPos, seqPos);
++
++              }
++              if (!results.isEmpty())
++              {
++                seqListener.highlightSequence(results);
++              }
++            }
++          }
++        }
++        else if (listener instanceof VamsasListener && !handlingVamsasMo)
++        {
++          ((VamsasListener) listener).mouseOverSequence(seq, indexpos,
++                  source);
++        }
++        else if (listener instanceof SecondaryStructureListener)
++        {
++          ((SecondaryStructureListener) listener).mouseOverSequence(seq,
++                  indexpos, seqPos);
++        }
++      }
++    }
++  }
++
++  /**
++   * Send suitable messages to a StructureListener to highlight atoms
++   * corresponding to the given sequence position(s)
++   * 
++   * @param sl
++   * @param seq
++   * @param positions
++   */
++  public void highlightStructure(StructureListener sl, SequenceI seq,
++          int... positions)
++  {
++    if (!sl.isListeningFor(seq))
++    {
++      return;
++    }
++    int atomNo;
++    List<AtomSpec> atoms = new ArrayList<AtomSpec>();
++    for (StructureMapping sm : mappings)
++    {
++      if (sm.sequence == seq
++              || sm.sequence == seq.getDatasetSequence()
++              || (sm.sequence.getDatasetSequence() != null && sm.sequence
++                      .getDatasetSequence() == seq.getDatasetSequence()))
++      {
++        for (int index : positions)
++        {
++          atomNo = sm.getAtomNum(index);
++
++          if (atomNo > 0)
++          {
++            atoms.add(new AtomSpec(sm.pdbfile, sm.pdbchain, sm
++                    .getPDBResNum(index), atomNo));
++          }
++        }
++      }
++    }
++    sl.highlightAtoms(atoms);
++  }
++
++  /**
++   * true if a mouse over event from an external (ie Vamsas) source is being
++   * handled
++   */
++  boolean handlingVamsasMo = false;
++
++  long lastmsg = 0;
++
++  /**
++   * as mouseOverSequence but only route event to SequenceListeners
++   * 
++   * @param sequenceI
++   * @param position
++   *          in an alignment sequence
++   */
++  public void mouseOverVamsasSequence(SequenceI sequenceI, int position,
++          VamsasSource source)
++  {
++    handlingVamsasMo = true;
++    long msg = sequenceI.hashCode() * (1 + position);
++    if (lastmsg != msg)
++    {
++      lastmsg = msg;
++      mouseOverSequence(sequenceI, position, -1, source);
++    }
++    handlingVamsasMo = false;
++  }
++
++  public Annotation[] colourSequenceFromStructure(SequenceI seq,
++          String pdbid)
++  {
++    return null;
++    // THIS WILL NOT BE AVAILABLE IN JALVIEW 2.3,
++    // UNTIL THE COLOUR BY ANNOTATION IS REWORKED
++    /*
++     * Annotation [] annotations = new Annotation[seq.getLength()];
++     * 
++     * StructureListener sl; int atomNo = 0; for (int i = 0; i <
++     * listeners.size(); i++) { if (listeners.elementAt(i) instanceof
++     * StructureListener) { sl = (StructureListener) listeners.elementAt(i);
++     * 
++     * for (int j = 0; j < mappings.length; j++) {
++     * 
++     * if (mappings[j].sequence == seq && mappings[j].getPdbId().equals(pdbid)
++     * && mappings[j].pdbfile.equals(sl.getPdbFile())) {
++     * System.out.println(pdbid+" "+mappings[j].getPdbId() +"
++     * "+mappings[j].pdbfile);
++     * 
++     * java.awt.Color col; for(int index=0; index<seq.getLength(); index++) {
++     * if(jalview.util.Comparison.isGap(seq.getCharAt(index))) continue;
++     * 
++     * atomNo = mappings[j].getAtomNum(seq.findPosition(index)); col =
++     * java.awt.Color.white; if (atomNo > 0) { col = sl.getColour(atomNo,
++     * mappings[j].getPDBResNum(index), mappings[j].pdbchain,
++     * mappings[j].pdbfile); }
++     * 
++     * annotations[index] = new Annotation("X",null,' ',0,col); } return
++     * annotations; } } } }
++     * 
++     * return annotations;
++     */
++  }
++
++  public void structureSelectionChanged()
++  {
++  }
++
++  public void sequenceSelectionChanged()
++  {
++  }
++
++  public void sequenceColoursChanged(Object source)
++  {
++    StructureListener sl;
++    for (int i = 0; i < listeners.size(); i++)
++    {
++      if (listeners.elementAt(i) instanceof StructureListener)
++      {
++        sl = (StructureListener) listeners.elementAt(i);
++        sl.updateColours(source);
++      }
++    }
++  }
++
++  public StructureMapping[] getMapping(String pdbfile)
++  {
++    List<StructureMapping> tmp = new ArrayList<StructureMapping>();
++    for (StructureMapping sm : mappings)
++    {
++      if (sm.pdbfile.equals(pdbfile))
++      {
++        tmp.add(sm);
++      }
++    }
++    return tmp.toArray(new StructureMapping[tmp.size()]);
++  }
++
++  /**
++   * Returns a readable description of all mappings for the given pdbfile to any
++   * of the given sequences
++   * 
++   * @param pdbfile
++   * @param seqs
++   * @return
++   */
++  public String printMappings(String pdbfile, List<SequenceI> seqs)
++  {
++    if (pdbfile == null || seqs == null || seqs.isEmpty())
++    {
++      return "";
++    }
++
++    StringBuilder sb = new StringBuilder(64);
++    for (StructureMapping sm : mappings)
++    {
++      if (sm.pdbfile.equals(pdbfile) && seqs.contains(sm.sequence))
++      {
++        sb.append(sm.mappingDetails);
++        sb.append(NEWLINE);
++        // separator makes it easier to read multiple mappings
++        sb.append("=====================");
++        sb.append(NEWLINE);
++      }
++    }
++    sb.append(NEWLINE);
++
++    return sb.toString();
++  }
++
++  /**
++   * Remove the given mapping
++   * 
++   * @param acf
++   */
++  public void deregisterMapping(AlignedCodonFrame acf)
++  {
++    if (acf != null)
++    {
++      boolean removed = seqmappings.remove(acf);
++      if (removed && seqmappings.isEmpty())
++      { // debug
++        System.out.println("All mappings removed");
++      }
++    }
++  }
++
++  /**
++   * Add each of the given codonFrames to the stored set, if not aready present.
++   * 
++   * @param mappings
++   */
++  public void registerMappings(List<AlignedCodonFrame> mappings)
++  {
++    if (mappings != null)
++    {
++      for (AlignedCodonFrame acf : mappings)
++      {
++        registerMapping(acf);
++      }
++    }
++  }
++
++  /**
++   * Add the given mapping to the stored set, unless already stored.
++   */
++  public void registerMapping(AlignedCodonFrame acf)
++  {
++    if (acf != null)
++    {
++      if (!seqmappings.contains(acf))
++      {
++        seqmappings.add(acf);
++      }
++    }
++  }
++
++  /**
++   * Resets this object to its initial state by removing all registered
++   * listeners, codon mappings, PDB file mappings
++   */
++  public void resetAll()
++  {
++    if (mappings != null)
++    {
++      mappings.clear();
++    }
++    if (seqmappings != null)
++    {
++      seqmappings.clear();
++    }
++    if (sel_listeners != null)
++    {
++      sel_listeners.clear();
++    }
++    if (listeners != null)
++    {
++      listeners.clear();
++    }
++    if (commandListeners != null)
++    {
++      commandListeners.clear();
++    }
++    if (view_listeners != null)
++    {
++      view_listeners.clear();
++    }
++    if (pdbFileNameId != null)
++    {
++      pdbFileNameId.clear();
++    }
++    if (pdbIdFileName != null)
++    {
++      pdbIdFileName.clear();
++    }
++  }
++
++  public void addSelectionListener(SelectionListener selecter)
++  {
++    if (!sel_listeners.contains(selecter))
++    {
++      sel_listeners.add(selecter);
++    }
++  }
++
++  public void removeSelectionListener(SelectionListener toremove)
++  {
++    if (sel_listeners.contains(toremove))
++    {
++      sel_listeners.remove(toremove);
++    }
++  }
++
++  public synchronized void sendSelection(
++          jalview.datamodel.SequenceGroup selection,
++          jalview.datamodel.ColumnSelection colsel, SelectionSource source)
++  {
++    for (SelectionListener slis : sel_listeners)
++    {
++      if (slis != source)
++      {
++        slis.selection(selection, colsel, source);
++      }
++    }
++  }
++
++  Vector<AlignmentViewPanelListener> view_listeners = new Vector<AlignmentViewPanelListener>();
++
++  public synchronized void sendViewPosition(
++          jalview.api.AlignmentViewPanel source, int startRes, int endRes,
++          int startSeq, int endSeq)
++  {
++
++    if (view_listeners != null && view_listeners.size() > 0)
++    {
++      Enumeration<AlignmentViewPanelListener> listeners = view_listeners
++              .elements();
++      while (listeners.hasMoreElements())
++      {
++        AlignmentViewPanelListener slis = listeners.nextElement();
++        if (slis != source)
++        {
++          slis.viewPosition(startRes, endRes, startSeq, endSeq, source);
++        }
++        ;
++      }
++    }
++  }
++
++  /**
++   * release all references associated with this manager provider
++   * 
++   * @param jalviewLite
++   */
++  public static void release(StructureSelectionManagerProvider jalviewLite)
++  {
++    // synchronized (instances)
++    {
++      if (instances == null)
++      {
++        return;
++      }
++      StructureSelectionManager mnger = (instances.get(jalviewLite));
++      if (mnger != null)
++      {
++        instances.remove(jalviewLite);
++        try
++        {
++          mnger.finalize();
++        } catch (Throwable x)
++        {
++        }
++      }
++    }
++  }
++
++  public void registerPDBEntry(PDBEntry pdbentry)
++  {
++    if (pdbentry.getFile() != null
++            && pdbentry.getFile().trim().length() > 0)
++    {
++      registerPDBFile(pdbentry.getId(), pdbentry.getFile());
++    }
++  }
++
++  public void addCommandListener(CommandListener cl)
++  {
++    if (!commandListeners.contains(cl))
++    {
++      commandListeners.add(cl);
++    }
++  }
++
++  public boolean hasCommandListener(CommandListener cl)
++  {
++    return this.commandListeners.contains(cl);
++  }
++
++  public boolean removeCommandListener(CommandListener l)
++  {
++    return commandListeners.remove(l);
++  }
++
++  /**
++   * Forward a command to any command listeners (except for the command's
++   * source).
++   * 
++   * @param command
++   *          the command to be broadcast (in its form after being performed)
++   * @param undo
++   *          if true, the command was being 'undone'
++   * @param source
++   */
++  public void commandPerformed(CommandI command, boolean undo,
++          VamsasSource source)
++  {
++    for (CommandListener listener : commandListeners)
++    {
++      listener.mirrorCommand(command, undo, this, source);
++    }
++  }
++
++  /**
++   * Returns a new CommandI representing the given command as mapped to the
++   * given sequences. If no mapping could be made, or the command is not of a
++   * mappable kind, returns null.
++   * 
++   * @param command
++   * @param undo
++   * @param mapTo
++   * @param gapChar
++   * @return
++   */
++  public CommandI mapCommand(CommandI command, boolean undo,
++          final AlignmentI mapTo, char gapChar)
++  {
++    if (command instanceof EditCommand)
++    {
++      return MappingUtils.mapEditCommand((EditCommand) command, undo,
++              mapTo, gapChar, seqmappings);
++    }
++    else if (command instanceof OrderCommand)
++    {
++      return MappingUtils.mapOrderCommand((OrderCommand) command, undo,
++              mapTo, seqmappings);
++    }
++    return null;
++  }
++
++  public List<AlignedCodonFrame> getSequenceMappings()
++  {
++    return seqmappings;
++  }
++
++}
index 0000000,0000000..e6b45f2
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,154 @@@
++package jalview.util;
++
++import jalview.ext.android.SparseIntArray;
++import jalview.ext.android.SparseShortArray;
++
++/**
++ * A class to count occurrences of characters with minimal memory footprint.
++ * Sparse arrays of short values are used to hold the counts, with automatic
++ * promotion to arrays of int if any count exceeds the maximum value for a
++ * short.
++ * 
++ * @author gmcarstairs
++ *
++ */
++public class SparseCount
++{
++  private static final int DEFAULT_PROFILE_SIZE = 2;
++
++  /*
++   * array of keys (chars) and values (counts)
++   * held either as shorts or (if shorts overflow) as ints 
++   */
++  private SparseShortArray shortProfile;
++
++  private SparseIntArray intProfile;
++
++  /*
++   * flag is set true after short overflow occurs
++   */
++  private boolean useInts;
++
++  /**
++   * Constructor which initially creates a new sparse array of short values to
++   * hold counts.
++   * 
++   * @param profileSize
++   */
++  public SparseCount(int profileSize)
++  {
++    this.shortProfile = new SparseShortArray(profileSize);
++  }
++
++  /**
++   * Constructor which allocates an initial count array for only two distinct
++   * values (the array will grow if needed)
++   */
++  public SparseCount()
++  {
++    this(DEFAULT_PROFILE_SIZE);
++  }
++
++  /**
++   * Adds the given value for the given key (or sets the initial value), and
++   * returns the new value
++   * 
++   * @param key
++   * @param value
++   */
++  public int add(int key, int value)
++  {
++    int newValue = 0;
++    if (useInts)
++    {
++      newValue = intProfile.add(key, value);
++    }
++    else
++    {
++      try {
++        newValue = shortProfile.add(key, value);
++      } catch (ArithmeticException e) {
++        handleOverflow();
++        newValue = intProfile.add(key, value);
++      }
++    }
++    return newValue;
++  }
++
++  /**
++   * Switch from counting shorts to counting ints
++   */
++  synchronized void handleOverflow()
++  {
++    int size = shortProfile.size();
++    intProfile = new SparseIntArray(size);
++    for (int i = 0; i < size; i++)
++    {
++      short key = shortProfile.keyAt(i);
++      short value = shortProfile.valueAt(i);
++      intProfile.put(key, value);
++    }
++    shortProfile = null;
++    useInts = true;
++  }
++
++  /**
++   * Returns the size of the profile (number of distinct items counted)
++   * 
++   * @return
++   */
++  public int size()
++  {
++    return useInts ? intProfile.size() : shortProfile.size();
++  }
++
++  /**
++   * Returns the value for the key (zero if no such key)
++   * 
++   * @param key
++   * @return
++   */
++  public int get(int key)
++  {
++    return useInts ? intProfile.get(key) : shortProfile.get(key);
++  }
++
++  /**
++   * Sets the value for the given key
++   * 
++   * @param key
++   * @param value
++   */
++  public void put(int key, int value)
++  {
++    if (useInts)
++    {
++      intProfile.put(key, value);
++    }
++    else
++    {
++      shortProfile.put(key, value);
++    }
++  }
++
++  public int keyAt(int k)
++  {
++    return useInts ? intProfile.keyAt(k) : shortProfile.keyAt(k);
++  }
++
++  public int valueAt(int k)
++  {
++    return useInts ? intProfile.valueAt(k) : shortProfile.valueAt(k);
++  }
++
++  /**
++   * Answers true if this object wraps arrays of int values, false if using
++   * short values
++   * 
++   * @return
++   */
++  boolean isUsingInt()
++  {
++    return useInts;
++  }
++}
@@@ -35,7 -35,7 +35,8 @@@ import jalview.datamodel.AlignmentView
  import jalview.datamodel.Annotation;
  import jalview.datamodel.CigarArray;
  import jalview.datamodel.ColumnSelection;
 +import jalview.datamodel.ContactListI;
+ import jalview.datamodel.HiddenColumns;
  import jalview.datamodel.HiddenSequences;
  import jalview.datamodel.ProfilesI;
  import jalview.datamodel.SearchResultsI;
index 0000000,0000000..6c2f70a
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,294 @@@
++/*
++ * 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.ws.dbsources;
++
++import jalview.api.FeatureSettingsModelI;
++import jalview.datamodel.AlignmentAnnotation;
++import jalview.datamodel.AlignmentI;
++import jalview.datamodel.DBRefEntry;
++import jalview.datamodel.DBRefSource;
++import jalview.datamodel.PDBEntry;
++import jalview.datamodel.SequenceI;
++import jalview.io.FormatAdapter;
++import jalview.io.PDBFeatureSettings;
++import jalview.util.MessageManager;
++import jalview.ws.ebi.EBIFetchClient;
++
++import java.util.ArrayList;
++import java.util.List;
++import java.util.Vector;
++
++import com.stevesoft.pat.Regex;
++
++/**
++ * @author JimP
++ * 
++ */
++public class Pdb extends EbiFileRetrievedProxy
++{
++  public Pdb()
++  {
++    super();
++  }
++
++  public static final String FEATURE_INSERTION = "INSERTION";
++
++  public static final String FEATURE_RES_NUM = "RESNUM";
++
++  private static String currentDefaultFormat = DBRefSource.PDB;
++
++  /*
++   * (non-Javadoc)
++   * 
++   * @see jalview.ws.DbSourceProxy#getAccessionSeparator()
++   */
++  @Override
++  public String getAccessionSeparator()
++  {
++    // TODO Auto-generated method stub
++    return null;
++  }
++
++  /*
++   * (non-Javadoc)
++   * 
++   * @see jalview.ws.DbSourceProxy#getAccessionValidator()
++   */
++  @Override
++  public Regex getAccessionValidator()
++  {
++    return new Regex("([1-9][0-9A-Za-z]{3}):?([ _A-Za-z0-9]?)");
++  }
++
++  /*
++   * (non-Javadoc)
++   * 
++   * @see jalview.ws.DbSourceProxy#getDbSource()
++   */
++  @Override
++  public String getDbSource()
++  {
++    return DBRefSource.PDB;
++  }
++
++  /*
++   * (non-Javadoc)
++   * 
++   * @see jalview.ws.DbSourceProxy#getDbVersion()
++   */
++  @Override
++  public String getDbVersion()
++  {
++    return "0";
++  }
++
++  /*
++   * (non-Javadoc)
++   * 
++   * @see jalview.ws.DbSourceProxy#getSequenceRecords(java.lang.String[])
++   */
++  @Override
++  public AlignmentI getSequenceRecords(String queries) throws Exception
++  {
++    AlignmentI pdbAlignment = null;
++    Vector result = new Vector();
++    String chain = null;
++    String id = null;
++    if (queries.indexOf(":") > -1)
++    {
++      chain = queries.substring(queries.indexOf(":") + 1);
++      id = queries.substring(0, queries.indexOf(":"));
++    }
++    else
++    {
++      id = queries;
++    }
++    if (queries.length() > 4 && chain == null)
++    {
++      chain = queries.substring(4, 5);
++      id = queries.substring(0, 4);
++    }
++    if (!isValidReference(id))
++    {
++      System.err.println("Ignoring invalid pdb query: '" + id + "'");
++      stopQuery();
++      return null;
++    }
++    String ext = getCurrentDefaultFormat().equalsIgnoreCase("mmcif") ? ".cif"
++            : ".xml";
++    EBIFetchClient ebi = new EBIFetchClient();
++    file = ebi.fetchDataAsFile("pdb:" + id,
++<<<<<<< HEAD
++            getCurrentDefaultFomart().toLowerCase(), ext)
++=======
++            getCurrentDefaultFormat().toLowerCase(), "raw", ext)
++>>>>>>> develop
++            .getAbsolutePath();
++    stopQuery();
++    if (file == null)
++    {
++      return null;
++    }
++    try
++    {
++
++      pdbAlignment = new FormatAdapter().readFile(file,
++              jalview.io.AppletFormatAdapter.FILE,
++              getCurrentDefaultFormat());
++      if (pdbAlignment != null)
++      {
++        List<SequenceI> toremove = new ArrayList<SequenceI>();
++        for (SequenceI pdbcs : pdbAlignment.getSequences())
++        {
++          String chid = null;
++          // Mapping map=null;
++          for (PDBEntry pid : pdbcs.getAllPDBEntries())
++          {
++            if (pid.getFile() == file)
++            {
++              chid = pid.getChainCode();
++
++            }
++            ;
++
++          }
++          if (chain == null
++                  || (chid != null && (chid.equals(chain)
++                          || chid.trim().equals(chain.trim()) || (chain
++                          .trim().length() == 0 && chid.equals("_")))))
++          {
++            pdbcs.setName(jalview.datamodel.DBRefSource.PDB + "|" + id
++                    + "|" + pdbcs.getName());
++            // Might need to add more metadata to the PDBEntry object
++            // like below
++            /*
++             * PDBEntry entry = new PDBEntry(); // Construct the PDBEntry
++             * entry.setId(id); if (entry.getProperty() == null)
++             * entry.setProperty(new Hashtable());
++             * entry.getProperty().put("chains", pdbchain.id + "=" +
++             * sq.getStart() + "-" + sq.getEnd());
++             * sq.getDatasetSequence().addPDBId(entry);
++             */
++            // Add PDB DB Refs
++            // We make a DBRefEtntry because we have obtained the PDB file from
++            // a
++            // verifiable source
++            // JBPNote - PDB DBRefEntry should also carry the chain and mapping
++            // information
++            DBRefEntry dbentry = new DBRefEntry(getDbSource(),
++                    getDbVersion(), (chid == null ? id : id + chid));
++            // dbentry.setMap()
++            pdbcs.addDBRef(dbentry);
++          }
++          else
++          {
++            // mark this sequence to be removed from the alignment
++            // - since it's not from the right chain
++            toremove.add(pdbcs);
++          }
++        }
++        // now remove marked sequences
++        for (SequenceI pdbcs : toremove)
++        {
++          pdbAlignment.deleteSequence(pdbcs);
++          if (pdbcs.getAnnotation() != null)
++          {
++            for (AlignmentAnnotation aa : pdbcs.getAnnotation())
++            {
++              pdbAlignment.deleteAnnotation(aa);
++            }
++          }
++        }
++      }
++
++      if (pdbAlignment == null || pdbAlignment.getHeight() < 1)
++      {
++        throw new Exception(MessageManager.formatMessage(
++                "exception.no_pdb_records_for_chain", new String[] { id,
++                    ((chain == null) ? "' '" : chain) }));
++      }
++
++    } catch (Exception ex) // Problem parsing PDB file
++    {
++      stopQuery();
++      throw (ex);
++    }
++    return pdbAlignment;
++  }
++
++  /*
++   * (non-Javadoc)
++   * 
++   * @see jalview.ws.DbSourceProxy#isValidReference(java.lang.String)
++   */
++  @Override
++  public boolean isValidReference(String accession)
++  {
++    Regex r = getAccessionValidator();
++    return r.search(accession.trim());
++  }
++
++  /**
++   * obtain human glyoxalase chain A sequence
++   */
++  @Override
++  public String getTestQuery()
++  {
++    return "1QIPA";
++  }
++
++  @Override
++  public String getDbName()
++  {
++    return "PDB"; // getDbSource();
++  }
++
++  @Override
++  public int getTier()
++  {
++    return 0;
++  }
++
++  public static String getCurrentDefaultFormat()
++  {
++    return currentDefaultFormat;
++  }
++
++  public static void setCurrentDefaultFormat(String currentDefaultFomart)
++  {
++    Pdb.currentDefaultFormat = currentDefaultFomart;
++  }
++
++  /**
++   * Returns a descriptor for suitable feature display settings with
++   * <ul>
++   * <li>ResNums or insertions features visible</li>
++   * <li>insertions features coloured red</li>
++   * <li>ResNum features coloured by label</li>
++   * <li>Insertions displayed above (on top of) ResNums</li>
++   * </ul>
++   */
++  @Override
++  public FeatureSettingsModelI getFeatureColourScheme()
++  {
++    return new PDBFeatureSettings();
++  }
++}