*/
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);
}
--- /dev/null
--- /dev/null
++/*
++ * 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);
++ }
++ }
++
++}
}
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;
+ }
}
*/
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);
}
--- /dev/null
--- /dev/null
++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;
++ }
++}
--- /dev/null
--- /dev/null
++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();
++
++}
--- /dev/null
--- /dev/null
++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;
++ }
++
++}
--- /dev/null
--- /dev/null
++package jalview.datamodel;
++
++public interface ProfilesI
++{
++
++ ProfileI get(int i);
++
++ int getStartColumn();
++
++ int getEndColumn();
++
++}
--- /dev/null
--- /dev/null
++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();
++ }
++}
--- /dev/null
--- /dev/null
++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();
++
++}
--- /dev/null
--- /dev/null
++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);
++}
--- /dev/null
--- /dev/null
++/*
++ * 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;
++ }
++
++}
--- /dev/null
--- /dev/null
++/*
++ * 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);
++ }
++}
--- /dev/null
--- /dev/null
++/*
++ * 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();
++ }
++}
--- /dev/null
--- /dev/null
++/*
++ * 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();
++ }
++}
--- /dev/null
--- /dev/null
++/*
++ * 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 };
++ }
++ }
++}
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;
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
--- /dev/null
+/**
+ *
+ */
+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());
+ }
+
+}
--- /dev/null
+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);
+
+}
--- /dev/null
--- /dev/null
++/*
++ * 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;
++ }
++
++}
--- /dev/null
--- /dev/null
++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;
++ }
++}
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;
--- /dev/null
--- /dev/null
++/*
++ * 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();
++ }
++}