import jalview.commands.EditCommand.Edit;
import jalview.datamodel.AlignmentI;
import jalview.datamodel.ColumnSelection;
+import jalview.datamodel.HiddenColumns;
+import jalview.datamodel.SearchResultMatchI;
import jalview.datamodel.SearchResults;
-import jalview.datamodel.SearchResults.Match;
+import jalview.datamodel.SearchResultsI;
import jalview.datamodel.Sequence;
import jalview.datamodel.SequenceFeature;
import jalview.datamodel.SequenceGroup;
import jalview.datamodel.SequenceI;
import jalview.io.SequenceAnnotationReport;
+import jalview.renderer.ResidueShaderI;
import jalview.schemes.ResidueProperties;
import jalview.structure.SelectionListener;
import jalview.structure.SelectionSource;
import jalview.util.Comparison;
import jalview.util.MappingUtils;
import jalview.util.MessageManager;
+import jalview.util.Platform;
import jalview.viewmodel.AlignmentViewport;
import java.awt.BorderLayout;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
-import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.ToolTipManager;
* @author $author$
* @version $Revision: 1.130 $
*/
-public class SeqPanel extends JPanel implements MouseListener,
- MouseMotionListener, MouseWheelListener, SequenceListener,
- SelectionListener
+public class SeqPanel extends JPanel
+ implements MouseListener, MouseMotionListener, MouseWheelListener,
+ SequenceListener, SelectionListener
{
/** DOCUMENT ME!! */
/** DOCUMENT ME!! */
public AlignmentPanel ap;
+ /*
+ * last column position for mouseMoved event
+ */
+ private int lastMouseColumn;
+
+ /*
+ * last sequence offset for mouseMoved event
+ */
+ private int lastMouseSeq;
+
protected int lastres;
protected int startseq;
private final SequenceAnnotationReport seqARep;
- StringBuffer tooltipText = new StringBuffer();
+ StringBuilder tooltipText = new StringBuilder();
String tmpString;
StructureSelectionManager ssm;
- SearchResults lastSearchResults;
+ SearchResultsI lastSearchResults;
/**
* Creates a new SeqPanel object.
ssm.addStructureViewerListener(this);
ssm.addSelectionListener(this);
}
+
+ lastMouseColumn = -1;
+ lastMouseSeq = -1;
}
int startWrapBlock = -1;
* @param evt
* @return
*/
- int findRes(MouseEvent evt)
+ int findColumn(MouseEvent evt)
{
int res = 0;
int x = evt.getX();
+ int startRes = av.getRanges().getStartRes();
if (av.getWrapAlignment())
{
+ hgap + seqCanvas.getAnnotationHeight();
int y = evt.getY();
- y -= hgap;
- x -= seqCanvas.LABEL_WEST;
+ y = Math.max(0, y - hgap);
+ x = Math.max(0, x - seqCanvas.getLabelWidthWest());
int cwidth = seqCanvas.getWrappedCanvasWidth(this.getWidth());
if (cwidth < 1)
}
wrappedBlock = y / cHeight;
- wrappedBlock += av.getStartRes() / cwidth;
-
- res = wrappedBlock * cwidth + x / av.getCharWidth();
-
+ wrappedBlock += startRes / cwidth;
+ // allow for wrapped view scrolled right (possible from Overview)
+ int startOffset = startRes % cwidth;
+ res = wrappedBlock * cwidth + startOffset
+ + +Math.min(cwidth - 1, x / av.getCharWidth());
}
else
{
// right-hand gutter
x = seqCanvas.getX() + seqCanvas.getWidth();
}
- res = (x / av.getCharWidth()) + av.getStartRes();
- if (res > av.getEndRes())
+ res = (x / av.getCharWidth()) + startRes;
+ if (res > av.getRanges().getEndRes())
{
// moused off right
- res = av.getEndRes();
+ res = av.getRanges().getEndRes();
}
}
if (av.hasHiddenColumns())
{
- res = av.getColumnSelection().adjustForHiddenColumns(res);
+ res = av.getAlignment().getHiddenColumns()
+ .adjustForHiddenColumns(res);
}
return res;
y -= hgap;
- seq = Math.min((y % cHeight) / av.getCharHeight(), av.getAlignment()
- .getHeight() - 1);
+ seq = Math.min((y % cHeight) / av.getCharHeight(),
+ av.getAlignment().getHeight() - 1);
}
else
{
- seq = Math.min((y / av.getCharHeight()) + av.getStartSeq(), av
- .getAlignment().getHeight() - 1);
+ seq = Math.min(
+ (y / av.getCharHeight()) + av.getRanges().getStartSeq(),
+ av.getAlignment().getHeight() - 1);
}
return seq;
if (editCommand != null && editCommand.getSize() > 0)
{
ap.alignFrame.addHistoryItem(editCommand);
- av.firePropertyChange("alignment", null, av.getAlignment()
- .getSequences());
+ av.firePropertyChange("alignment", null,
+ av.getAlignment().getSequences());
}
} finally
{
void setCursorRow()
{
seqCanvas.cursorY = getKeyboardNo1() - 1;
- scrollToVisible();
+ scrollToVisible(true);
}
void setCursorColumn()
{
seqCanvas.cursorX = getKeyboardNo1() - 1;
- scrollToVisible();
+ scrollToVisible(true);
}
void setCursorRowAndColumn()
{
seqCanvas.cursorX = getKeyboardNo1() - 1;
seqCanvas.cursorY = getKeyboardNo2() - 1;
- scrollToVisible();
+ scrollToVisible(true);
}
}
SequenceI sequence = av.getAlignment().getSequenceAt(seqCanvas.cursorY);
seqCanvas.cursorX = sequence.findIndex(getKeyboardNo1()) - 1;
- scrollToVisible();
+ scrollToVisible(true);
}
void moveCursor(int dx, int dy)
{
seqCanvas.cursorX += dx;
seqCanvas.cursorY += dy;
- if (av.hasHiddenColumns()
- && !av.getColumnSelection().isVisible(seqCanvas.cursorX))
+
+ HiddenColumns hidden = av.getAlignment().getHiddenColumns();
+
+ if (av.hasHiddenColumns() && !hidden.isVisible(seqCanvas.cursorX))
{
int original = seqCanvas.cursorX - dx;
int maxWidth = av.getAlignment().getWidth();
- while (!av.getColumnSelection().isVisible(seqCanvas.cursorX)
+ while (!hidden.isVisible(seqCanvas.cursorX)
&& seqCanvas.cursorX < maxWidth && seqCanvas.cursorX > 0)
{
seqCanvas.cursorX += dx;
}
if (seqCanvas.cursorX >= maxWidth
- || !av.getColumnSelection().isVisible(seqCanvas.cursorX))
+ || !hidden.isVisible(seqCanvas.cursorX))
{
seqCanvas.cursorX = original;
}
}
- scrollToVisible();
+ scrollToVisible(false);
}
- void scrollToVisible()
+ /**
+ * Scroll to make the cursor visible in the viewport.
+ *
+ * @param jump
+ * just jump to the location rather than scrolling
+ */
+ void scrollToVisible(boolean jump)
{
if (seqCanvas.cursorX < 0)
{
}
endEditing();
- if (av.getWrapAlignment())
+
+ boolean repaintNeeded = true;
+ if (jump)
{
- ap.scrollToWrappedVisible(seqCanvas.cursorX);
+ // only need to repaint if the viewport did not move, as otherwise it will
+ // get a repaint
+ repaintNeeded = !av.getRanges().setViewportLocation(seqCanvas.cursorX,
+ seqCanvas.cursorY);
}
else
{
- while (seqCanvas.cursorY < av.startSeq)
+ if (av.getWrapAlignment())
{
- ap.scrollUp(true);
+ av.getRanges().scrollToWrappedVisible(seqCanvas.cursorX);
}
- while (seqCanvas.cursorY + 1 > av.endSeq)
+ else
{
- ap.scrollUp(false);
- }
- if (!av.getWrapAlignment())
- {
- while (seqCanvas.cursorX < av.getColumnSelection()
- .adjustForHiddenColumns(av.startRes))
- {
- if (!ap.scrollRight(false))
- {
- break;
- }
- }
- while (seqCanvas.cursorX > av.getColumnSelection()
- .adjustForHiddenColumns(av.endRes))
- {
- if (!ap.scrollRight(true))
- {
- break;
- }
- }
+ av.getRanges().scrollToVisible(seqCanvas.cursorX,
+ seqCanvas.cursorY);
}
}
- setStatusMessage(av.getAlignment().getSequenceAt(seqCanvas.cursorY),
+
+ if (av.getAlignment().getHiddenColumns().isVisible(seqCanvas.cursorX))
+ {
+ setStatusMessage(av.getAlignment().getSequenceAt(seqCanvas.cursorY),
seqCanvas.cursorX, seqCanvas.cursorY);
+ }
- seqCanvas.repaint();
+ if (repaintNeeded)
+ {
+ seqCanvas.repaint();
+ }
}
+
void setSelectionAreaAtCursor(boolean topLeft)
{
SequenceI sequence = av.getAlignment().getSequenceAt(seqCanvas.cursorY);
av.setSelectionGroup(sg);
}
- ap.paintAlignment(false);
+ ap.paintAlignment(false, false);
av.sendSelection();
}
@Override
public void mouseReleased(MouseEvent evt)
{
+ boolean didDrag = mouseDragging; // did we come here after a drag
mouseDragging = false;
mouseWheelPressed = false;
+ if (evt.isPopupTrigger()) // Windows: mouseReleased
+ {
+ showPopupMenu(evt);
+ evt.consume();
+ return;
+ }
+
if (!editingSeqs)
{
- doMouseReleasedDefineMode(evt);
+ doMouseReleasedDefineMode(evt, didDrag);
return;
}
return;
}
- if (evt.isShiftDown() || evt.isAltDown() || evt.isControlDown())
+ boolean isControlDown = Platform.isControlDown(evt);
+ if (evt.isShiftDown() || isControlDown)
{
- if (evt.isAltDown() || evt.isControlDown())
+ editingSeqs = true;
+ if (isControlDown)
{
groupEditing = true;
}
- editingSeqs = true;
}
else
{
}
int seq = findSeq(evt);
- int res = findRes(evt);
+ int res = findColumn(evt);
if (seq < 0 || res < 0)
{
* the start of the highlighted region.
*/
@Override
- public void highlightSequence(SearchResults results)
+ public void highlightSequence(SearchResultsI results)
{
if (results == null || results.equals(lastSearchResults))
{
}
lastSearchResults = results;
+ boolean wasScrolled = false;
+
if (av.isFollowHighlight())
{
- /*
- * if scrollToPosition requires a scroll adjustment, this flag prevents
- * another scroll event being propagated back to the originator
- *
- * @see AlignmentPanel#adjustmentValueChanged
- */
- ap.setDontScrollComplement(true);
- if (ap.scrollToPosition(results, false))
+ // don't allow highlight of protein/cDNA to also scroll a complementary
+ // panel,as this sets up a feedback loop (scrolling panel 1 causes moused
+ // over residue to change abruptly, causing highlighted residue in panel 2
+ // to change, causing a scroll in panel 1 etc)
+ ap.setToScrollComplementPanel(false);
+ wasScrolled = ap.scrollToPosition(results, false);
+ if (wasScrolled)
{
seqCanvas.revalidate();
}
+ ap.setToScrollComplementPanel(true);
+ }
+
+ boolean noFastPaint = wasScrolled && av.getWrapAlignment();
+ if (seqCanvas.highlightSearchResults(results, noFastPaint))
+ {
+ setStatusMessage(results);
}
- setStatusMessage(results);
- seqCanvas.highlightSearchResults(results);
}
@Override
}
/**
- * DOCUMENT ME!
+ * Action on mouse movement is to update the status bar to show the current
+ * sequence position, and (if features are shown) to show any features at the
+ * position in a tooltip. Does nothing if the mouse move does not change
+ * residue position.
*
* @param evt
- * DOCUMENT ME!
*/
@Override
public void mouseMoved(MouseEvent evt)
mouseDragged(evt);
}
- int res = findRes(evt);
- int seq = findSeq(evt);
- int pos;
- if (res < 0 || seq < 0 || seq >= av.getAlignment().getHeight())
+ final int column = findColumn(evt);
+ final int seq = findSeq(evt);
+
+ if (column < 0 || seq < 0 || seq >= av.getAlignment().getHeight())
{
+ lastMouseSeq = -1;
return;
}
+ if (column == lastMouseColumn && seq == lastMouseSeq)
+ {
+ /*
+ * just a pixel move without change of residue
+ */
+ return;
+ }
+ lastMouseColumn = column;
+ lastMouseSeq = seq;
SequenceI sequence = av.getAlignment().getSequenceAt(seq);
- if (res >= sequence.getLength())
+ if (column >= sequence.getLength())
{
return;
}
- pos = setStatusMessage(sequence, res, seq);
- if (ssm != null && pos > -1)
+ /*
+ * set status bar message, returning residue position in sequence
+ */
+ boolean isGapped = Comparison.isGap(sequence.getCharAt(column));
+ final int pos = setStatusMessage(sequence, column, seq);
+ if (ssm != null && !isGapped)
{
- mouseOverSequence(sequence, res, pos);
+ mouseOverSequence(sequence, column, pos);
}
tooltipText.setLength(6); // Cuts the buffer back to <html>
{
for (int g = 0; g < groups.length; g++)
{
- if (groups[g].getStartRes() <= res && groups[g].getEndRes() >= res)
+ if (groups[g].getStartRes() <= column
+ && groups[g].getEndRes() >= column)
{
if (!groups[g].getName().startsWith("JTreeGroup")
&& !groups[g].getName().startsWith("JGroup"))
}
}
- // use aa to see if the mouse pointer is on a
+ /*
+ * add any features at the position to the tooltip; if over a gap, only
+ * add features that straddle the gap (pos may be the residue before or
+ * after the gap)
+ */
if (av.isShowSequenceFeatures())
{
- int rpos;
List<SequenceFeature> features = ap.getFeatureRenderer()
- .findFeaturesAtRes(sequence.getDatasetSequence(),
- rpos = sequence.findPosition(res));
- seqARep.appendFeatures(tooltipText, rpos, features,
+ .findFeaturesAtColumn(sequence, column + 1);
+ seqARep.appendFeatures(tooltipText, pos, features,
this.ap.getSeqPanel().seqCanvas.fr.getMinMax());
}
- if (tooltipText.length() == 6) // <html></html>
+ if (tooltipText.length() == 6) // <html>
{
setToolTipText(null);
lastTooltip = null;
}
else
{
- if (lastTooltip == null
- || !lastTooltip.equals(tooltipText.toString()))
+ String textString = tooltipText.toString();
+ if (lastTooltip == null || !lastTooltip.equals(textString))
{
- String formatedTooltipText = JvSwingUtils.wrapTooltip(true,
- tooltipText.toString());
- // String formatedTooltipText = tooltipText.toString();
- setToolTipText(formatedTooltipText);
- lastTooltip = tooltipText.toString();
+ String formattedTooltipText = JvSwingUtils.wrapTooltip(true,
+ textString);
+ setToolTipText(formattedTooltipText);
+ lastTooltip = textString;
}
-
}
-
}
private Point lastp = null;
Point p = lastp;
if (!event.isShiftDown() || p == null)
{
- p = (tooltipText != null && tooltipText.length() > 6) ? new Point(
- event.getX() + wdth, event.getY() - 20) : null;
+ p = (tooltipText != null && tooltipText.length() > 6)
+ ? new Point(event.getX() + wdth, event.getY() - 20)
+ : null;
}
/*
* TODO: try to modify position region is not obcured by tooltip
String lastTooltip;
/**
- * Set status message in alignment panel
+ * set when the current UI interaction has resulted in a change that requires
+ * shading in overviews and structures to be recalculated. this could be
+ * changed to a something more expressive that indicates what actually has
+ * changed, so selective redraws can be applied (ie. only structures, only
+ * overview, etc)
+ */
+ private boolean updateOverviewAndStructs = false; // TODO: refactor to avcontroller
+
+ /**
+ * set if av.getSelectionGroup() refers to a group that is defined on the
+ * alignment view, rather than a transient selection
+ */
+ // private boolean editingDefinedGroup = false; // TODO: refactor to
+ // avcontroller or viewModel
+
+ /**
+ * Sets the status message in alignment panel, showing the sequence number
+ * (index) and id, and residue and residue position if not at a gap, for the
+ * given sequence and column position. Returns the residue position returned
+ * by Sequence.findPosition. Note this may be for the nearest adjacent residue
+ * if at a gapped position.
*
* @param sequence
* aligned sequence object
- * @param res
+ * @param column
* alignment column
- * @param seq
+ * @param seqIndex
* index of sequence in alignment
- * @return position of res in sequence
+ * @return sequence position of residue at column, or adjacent residue if at a
+ * gap
+ */
+ int setStatusMessage(SequenceI sequence, final int column, int seqIndex)
+ {
+ char sequenceChar = sequence.getCharAt(column);
+ int pos = sequence.findPosition(column);
+ setStatusMessage(sequence, seqIndex, sequenceChar, pos);
+
+ return pos;
+ }
+
+ /**
+ * Builds the status message for the current cursor location and writes it to
+ * the status bar, for example
+ *
+ * <pre>
+ * Sequence 3 ID: FER1_SOLLC
+ * Sequence 5 ID: FER1_PEA Residue: THR (4)
+ * Sequence 5 ID: FER1_PEA Residue: B (3)
+ * Sequence 6 ID: O.niloticus.3 Nucleotide: Uracil (2)
+ * </pre>
+ *
+ * @param sequence
+ * @param seqIndex
+ * sequence position in the alignment (1..)
+ * @param sequenceChar
+ * the character under the cursor
+ * @param residuePos
+ * the sequence residue position (if not over a gap)
*/
- int setStatusMessage(SequenceI sequence, int res, int seq)
+ protected void setStatusMessage(SequenceI sequence, int seqIndex,
+ char sequenceChar, int residuePos)
{
StringBuilder text = new StringBuilder(32);
/*
* Sequence number (if known), and sequence name.
*/
- String seqno = seq == -1 ? "" : " " + (seq + 1);
- text.append("Sequence" + seqno + " ID: " + sequence.getName());
+ String seqno = seqIndex == -1 ? "" : " " + (seqIndex + 1);
+ text.append("Sequence").append(seqno).append(" ID: ")
+ .append(sequence.getName());
String residue = null;
+
/*
* Try to translate the display character to residue name (null for gap).
*/
- final String displayChar = String.valueOf(sequence.getCharAt(res));
- if (av.getAlignment().isNucleotide())
+ boolean isGapped = Comparison.isGap(sequenceChar);
+
+ if (!isGapped)
{
- residue = ResidueProperties.nucleotideName.get(displayChar);
- if (residue != null)
+ boolean nucleotide = av.getAlignment().isNucleotide();
+ String displayChar = String.valueOf(sequenceChar);
+ if (nucleotide)
{
- text.append(" Nucleotide: ").append(residue);
+ residue = ResidueProperties.nucleotideName.get(displayChar);
}
- }
- else
- {
- residue = "X".equalsIgnoreCase(displayChar) ? "X" : ("*"
- .equals(displayChar) ? "STOP" : ResidueProperties.aa2Triplet
- .get(displayChar));
- if (residue != null)
+ else
{
- text.append(" Residue: ").append(residue);
+ residue = "X".equalsIgnoreCase(displayChar) ? "X"
+ : ("*".equals(displayChar) ? "STOP"
+ : ResidueProperties.aa2Triplet.get(displayChar));
}
- }
+ text.append(" ").append(nucleotide ? "Nucleotide" : "Residue")
+ .append(": ").append(residue == null ? displayChar : residue);
- int pos = -1;
- if (residue != null)
- {
- pos = sequence.findPosition(res);
- text.append(" (").append(Integer.toString(pos)).append(")");
+ text.append(" (").append(Integer.toString(residuePos)).append(")");
}
ap.alignFrame.statusBar.setText(text.toString());
- return pos;
}
/**
*
* @param results
*/
- private void setStatusMessage(SearchResults results)
+ private void setStatusMessage(SearchResultsI results)
{
AlignmentI al = this.av.getAlignment();
int sequenceIndex = al.findIndex(results);
return;
}
SequenceI ds = al.getSequenceAt(sequenceIndex).getDatasetSequence();
- for (Match m : results.getResults())
+ for (SearchResultMatchI m : results.getResults())
{
SequenceI seq = m.getSequence();
if (seq.getDatasetSequence() != null)
if (seq == ds)
{
- /*
- * Convert position in sequence (base 1) to sequence character array
- * index (base 0)
- */
- int start = m.getStart() - m.getSequence().getStart();
- setStatusMessage(seq, start, sequenceIndex);
+ int start = m.getStart();
+ setStatusMessage(seq, sequenceIndex, seq.getCharAt(start - 1),
+ start);
return;
}
}
}
/**
- * DOCUMENT ME!
- *
- * @param evt
- * DOCUMENT ME!
+ * {@inheritDoc}
*/
@Override
public void mouseDragged(MouseEvent evt)
{
if (mouseWheelPressed)
{
+ boolean inSplitFrame = ap.av.getCodingComplement() != null;
+ boolean copyChanges = inSplitFrame && av.isProteinFontAsCdna();
+
int oldWidth = av.getCharWidth();
// Which is bigger, left-right or up-down?
- if (Math.abs(evt.getY() - lastMousePress.getY()) > Math.abs(evt
- .getX() - lastMousePress.getX()))
+ if (Math.abs(evt.getY() - lastMousePress.getY()) > Math
+ .abs(evt.getX() - lastMousePress.getX()))
{
+ /*
+ * on drag up or down, decrement or increment font size
+ */
int fontSize = av.font.getSize();
+ boolean fontChanged = false;
if (evt.getY() < lastMousePress.getY())
{
+ fontChanged = true;
fontSize--;
}
else if (evt.getY() > lastMousePress.getY())
{
+ fontChanged = true;
fontSize++;
}
fontSize = 1;
}
- av.setFont(
- new Font(av.font.getName(), av.font.getStyle(), fontSize),
- true);
- av.setCharWidth(oldWidth);
- ap.fontChanged();
+ if (fontChanged)
+ {
+ Font newFont = new Font(av.font.getName(), av.font.getStyle(),
+ fontSize);
+ av.setFont(newFont, true);
+ av.setCharWidth(oldWidth);
+ ap.fontChanged();
+ if (copyChanges)
+ {
+ ap.av.getCodingComplement().setFont(newFont, true);
+ SplitFrame splitFrame = (SplitFrame) ap.alignFrame
+ .getSplitViewContainer();
+ splitFrame.adjustLayout();
+ splitFrame.repaint();
+ }
+ }
}
else
{
+ /*
+ * on drag left or right, decrement or increment character width
+ */
+ int newWidth = 0;
if (evt.getX() < lastMousePress.getX() && av.getCharWidth() > 1)
{
- av.setCharWidth(av.getCharWidth() - 1);
+ newWidth = av.getCharWidth() - 1;
+ av.setCharWidth(newWidth);
}
else if (evt.getX() > lastMousePress.getX())
{
- av.setCharWidth(av.getCharWidth() + 1);
+ newWidth = av.getCharWidth() + 1;
+ av.setCharWidth(newWidth);
+ }
+ if (newWidth > 0)
+ {
+ ap.paintAlignment(false, false);
+ if (copyChanges)
+ {
+ /*
+ * need to ensure newWidth is set on cdna, regardless of which
+ * panel the mouse drag happened in; protein will compute its
+ * character width as 1:1 or 3:1
+ */
+ av.getCodingComplement().setCharWidth(newWidth);
+ SplitFrame splitFrame = (SplitFrame) ap.alignFrame
+ .getSplitViewContainer();
+ splitFrame.adjustLayout();
+ splitFrame.repaint();
+ }
}
-
- ap.paintAlignment(false);
}
FontMetrics fm = getFontMetrics(av.getFont());
return;
}
- int res = findRes(evt);
+ int res = findColumn(evt);
if (res < 0)
{
}
mouseDragging = true;
- if (scrollThread != null)
+ if ((scrollThread != null) && (scrollThread.isRunning()))
{
scrollThread.setEvent(evt);
}
}
if (editCommand == null)
{
- editCommand = new EditCommand(MessageManager.formatMessage(
- "label.edit_params", new String[] { label }));
+ editCommand = new EditCommand(MessageManager
+ .formatMessage("label.edit_params", new String[]
+ { label }));
}
}
ap.alignFrame.statusBar.setText(message.toString());
// Are we editing within a selection group?
- if (groupEditing
- || (sg != null && sg.getSequences(av.getHiddenRepSequences())
- .contains(seq)))
+ if (groupEditing || (sg != null
+ && sg.getSequences(av.getHiddenRepSequences()).contains(seq)))
{
fixedColumns = true;
if (av.hasHiddenColumns())
{
fixedColumns = true;
- int y1 = av.getColumnSelection().getHiddenBoundaryLeft(startres);
- int y2 = av.getColumnSelection().getHiddenBoundaryRight(startres);
+ int y1 = av.getAlignment().getHiddenColumns()
+ .getHiddenBoundaryLeft(startres);
+ int y2 = av.getAlignment().getHiddenColumns()
+ .getHiddenBoundaryRight(startres);
if ((insertGap && startres > y1 && lastres < y1)
|| (!insertGap && startres < y2 && lastres > y2))
// Find the next gap before the end
// of the visible region boundary
boolean blank = false;
- for (fixedRight = fixedRight; fixedRight > lastres; fixedRight--)
+ for (; fixedRight > lastres; fixedRight--)
{
blank = true;
{
if (sg.getSize() == av.getAlignment().getHeight())
{
- if ((av.hasHiddenColumns() && startres < av
- .getColumnSelection().getHiddenBoundaryRight(startres)))
+ if ((av.hasHiddenColumns() && startres < av.getAlignment()
+ .getHiddenColumns().getHiddenBoundaryRight(startres)))
{
endEditing();
return;
}
else
{
- appendEdit(Action.INSERT_GAP, groupSeqs, startres, startres
- - lastres);
+ appendEdit(Action.INSERT_GAP, groupSeqs, startres,
+ startres - lastres);
}
}
else
}
else
{
- appendEdit(Action.DELETE_GAP, groupSeqs, startres, lastres
- - startres);
+ appendEdit(Action.DELETE_GAP, groupSeqs, startres,
+ lastres - startres);
}
}
oldSeq = 0;
}
- if (scrollThread != null)
+ if ((scrollThread != null) && (scrollThread.isRunning()))
{
- scrollThread.running = false;
+ scrollThread.stopScrolling();
scrollThread = null;
}
}
return;
}
- if (mouseDragging)
+ if (mouseDragging && scrollThread == null)
{
scrollThread = new ScrollThread();
}
}
+ /**
+ * Handler for double-click on a position with one or more sequence features.
+ * Opens the Amend Features dialog to allow feature details to be amended, or
+ * the feature deleted.
+ */
@Override
public void mouseClicked(MouseEvent evt)
{
av.setSelectionGroup(null);
}
+ int column = findColumn(evt);
+
+ /*
+ * find features at the position (if not gapped), or straddling
+ * the position (if at a gap)
+ */
List<SequenceFeature> features = seqCanvas.getFeatureRenderer()
- .findFeaturesAtRes(sequence.getDatasetSequence(),
- sequence.findPosition(findRes(evt)));
+ .findFeaturesAtColumn(sequence, column + 1);
- if (features != null && features.size() > 0)
+ if (!features.isEmpty())
{
- SearchResults highlight = new SearchResults();
+ /*
+ * highlight the first feature at the position on the alignment
+ */
+ SearchResultsI highlight = new SearchResults();
highlight.addResult(sequence, features.get(0).getBegin(), features
.get(0).getEnd());
- seqCanvas.highlightSearchResults(highlight);
- }
- if (features != null && features.size() > 0)
- {
- seqCanvas.getFeatureRenderer().amendFeatures(
- new SequenceI[] { sequence },
- features.toArray(new SequenceFeature[features.size()]),
- false, ap);
+ seqCanvas.highlightSearchResults(highlight, false);
- seqCanvas.highlightSearchResults(null);
+ /*
+ * open the Amend Features dialog; clear highlighting afterwards,
+ * whether changes were made or not
+ */
+ List<SequenceI> seqs = Collections.singletonList(sequence);
+ seqCanvas.getFeatureRenderer().amendFeatures(seqs, features, false,
+ ap);
+ av.setSearchResults(null); // clear highlighting
+ seqCanvas.repaint(); // draw new/amended features
}
}
}
{
if (e.isShiftDown())
{
- ap.scrollRight(true);
+ av.getRanges().scrollRight(true);
}
else
{
- ap.scrollUp(false);
+ av.getRanges().scrollUp(false);
}
}
else
{
if (e.isShiftDown())
{
- ap.scrollRight(false);
+ av.getRanges().scrollRight(false);
}
else
{
- ap.scrollUp(true);
+ av.getRanges().scrollUp(true);
}
}
- // TODO Update tooltip for new position.
+
+ /*
+ * update status bar and tooltip for new position
+ * (need to synthesize a mouse movement to refresh tooltip)
+ */
+ mouseMoved(e);
+ ToolTipManager.sharedInstance().mouseMoved(e);
}
/**
*/
public void doMousePressedDefineMode(MouseEvent evt)
{
- int res = findRes(evt);
- int seq = findSeq(evt);
+ final int res = findColumn(evt);
+ final int seq = findSeq(evt);
oldSeq = seq;
+ updateOverviewAndStructs = false;
startWrapBlock = wrappedBlock;
if (av.getWrapAlignment() && seq > av.getAlignment().getHeight())
{
- JOptionPane.showInternalMessageDialog(Desktop.desktop, MessageManager
- .getString("label.cannot_edit_annotations_in_wrapped_view"),
+ JvOptionPane.showInternalMessageDialog(Desktop.desktop,
+ MessageManager.getString(
+ "label.cannot_edit_annotations_in_wrapped_view"),
MessageManager.getString("label.wrapped_view_no_edit"),
- JOptionPane.WARNING_MESSAGE);
+ JvOptionPane.WARNING_MESSAGE);
return;
}
stretchGroup = av.getSelectionGroup();
- if (stretchGroup == null)
+ if (stretchGroup == null || !stretchGroup.contains(sequence, res))
{
- stretchGroup = av.getAlignment().findGroup(sequence);
-
- if ((stretchGroup != null) && (res > stretchGroup.getStartRes())
- && (res < stretchGroup.getEndRes()))
+ stretchGroup = av.getAlignment().findGroup(sequence, res);
+ if (stretchGroup != null)
{
+ // only update the current selection if the popup menu has a group to
+ // focus on
av.setSelectionGroup(stretchGroup);
}
- else
- {
- stretchGroup = null;
- }
}
- else if (!stretchGroup.getSequences(null).contains(sequence)
- || (stretchGroup.getStartRes() > res)
- || (stretchGroup.getEndRes() < res))
- {
- stretchGroup = null;
-
- SequenceGroup[] allGroups = av.getAlignment().findAllGroups(sequence);
-
- if (allGroups != null)
- {
- for (int i = 0; i < allGroups.length; i++)
- {
- if ((allGroups[i].getStartRes() <= res)
- && (allGroups[i].getEndRes() >= res))
- {
- stretchGroup = allGroups[i];
- break;
- }
- }
- }
-
- av.setSelectionGroup(stretchGroup);
+ if (evt.isPopupTrigger()) // Mac: mousePressed
+ {
+ showPopupMenu(evt);
+ return;
}
- if (evt.isPopupTrigger())
+ /*
+ * defer right-mouse click handling to mouseReleased on Windows
+ * (where isPopupTrigger() will answer true)
+ * NB isRightMouseButton is also true for Cmd-click on Mac
+ */
+ if (SwingUtilities.isRightMouseButton(evt) && !Platform.isAMac())
{
- List<SequenceFeature> allFeatures = ap.getFeatureRenderer()
- .findFeaturesAtRes(sequence.getDatasetSequence(),
- sequence.findPosition(res));
- List<String> links = new ArrayList<String>();
- for (SequenceFeature sf : allFeatures)
- {
- if (sf.links != null)
- {
- for (String link : sf.links)
- {
- links.add(link);
- }
- }
- }
-
- PopupMenu pop = new PopupMenu(ap, null, links);
- pop.show(this, evt.getX(), evt.getY());
return;
}
if (av.cursorMode)
{
- seqCanvas.cursorX = findRes(evt);
+ seqCanvas.cursorX = findColumn(evt);
seqCanvas.cursorY = findSeq(evt);
seqCanvas.repaint();
return;
if (stretchGroup == null)
{
- // Only if left mouse button do we want to change group sizes
+ createStretchGroup(res, sequence);
+ }
- // define a new group here
- SequenceGroup sg = new SequenceGroup();
- sg.setStartRes(res);
- sg.setEndRes(res);
- sg.addSequence(sequence, false);
- av.setSelectionGroup(sg);
+ if (stretchGroup != null)
+ {
+ stretchGroup.addPropertyChangeListener(seqCanvas);
+ }
- stretchGroup = sg;
+ seqCanvas.repaint();
+ }
- if (av.getConservationSelected())
- {
- SliderPanel.setConservationSlider(ap, av.getGlobalColourScheme(),
- "Background");
- }
+ private void createStretchGroup(int res, SequenceI sequence)
+ {
+ // Only if left mouse button do we want to change group sizes
+ // define a new group here
+ SequenceGroup sg = new SequenceGroup();
+ sg.setStartRes(res);
+ sg.setEndRes(res);
+ sg.addSequence(sequence, false);
+ av.setSelectionGroup(sg);
+ stretchGroup = sg;
- if (av.getAbovePIDThreshold())
- {
- SliderPanel.setPIDSliderSource(ap, av.getGlobalColourScheme(),
- "Background");
- }
- if ((stretchGroup != null) && (stretchGroup.getEndRes() == res))
- {
- // Edit end res position of selected group
- changeEndRes = true;
- }
- else if ((stretchGroup != null)
- && (stretchGroup.getStartRes() == res))
+ if (av.getConservationSelected())
+ {
+ SliderPanel.setConservationSlider(ap, av.getResidueShading(),
+ ap.getViewName());
+ }
+
+ if (av.getAbovePIDThreshold())
+ {
+ SliderPanel.setPIDSliderSource(ap, av.getResidueShading(),
+ ap.getViewName());
+ }
+ // TODO: stretchGroup will always be not null. Is this a merge error ?
+ // or is there a threading issue here?
+ if ((stretchGroup != null) && (stretchGroup.getEndRes() == res))
+ {
+ // Edit end res position of selected group
+ changeEndRes = true;
+ }
+ else if ((stretchGroup != null) && (stretchGroup.getStartRes() == res))
+ {
+ // Edit end res position of selected group
+ changeStartRes = true;
+ }
+ stretchGroup.getWidth();
+
+ }
+
+ /**
+ * Build and show a pop-up menu at the right-click mouse position
+ *
+ * @param evt
+ * @param res
+ * @param sequences
+ */
+ void showPopupMenu(MouseEvent evt)
+ {
+ final int column = findColumn(evt);
+ final int seq = findSeq(evt);
+ SequenceI sequence = av.getAlignment().getSequenceAt(seq);
+ List<SequenceFeature> allFeatures = ap.getFeatureRenderer()
+ .findFeaturesAtColumn(sequence, column + 1);
+ List<String> links = new ArrayList<>();
+ for (SequenceFeature sf : allFeatures)
+ {
+ if (sf.links != null)
{
- // Edit end res position of selected group
- changeStartRes = true;
+ for (String link : sf.links)
+ {
+ links.add(link);
+ }
}
- stretchGroup.getWidth();
}
- seqCanvas.repaint();
+ PopupMenu pop = new PopupMenu(ap, null, links);
+ pop.show(this, evt.getX(), evt.getY());
}
/**
- * DOCUMENT ME!
+ * Update the display after mouse up on a selection or group
*
* @param evt
- * DOCUMENT ME!
+ * mouse released event details
+ * @param afterDrag
+ * true if this event is happening after a mouse drag (rather than a
+ * mouse down)
*/
- public void doMouseReleasedDefineMode(MouseEvent evt)
+ public void doMouseReleasedDefineMode(MouseEvent evt, boolean afterDrag)
{
if (stretchGroup == null)
{
return;
}
- stretchGroup.recalcConservation(); // always do this - annotation has own
- // state
+ stretchGroup.removePropertyChangeListener(seqCanvas);
+
+ // always do this - annotation has own state
+ // but defer colourscheme update until hidden sequences are passed in
+ boolean vischange = stretchGroup.recalcConservation(true);
+ updateOverviewAndStructs |= vischange && av.isSelectionDefinedGroup()
+ && afterDrag;
if (stretchGroup.cs != null)
{
stretchGroup.cs.alignmentChanged(stretchGroup,
av.getHiddenRepSequences());
+ ResidueShaderI groupColourScheme = stretchGroup
+ .getGroupColourScheme();
+ String name = stretchGroup.getName();
if (stretchGroup.cs.conservationApplied())
{
- SliderPanel.setConservationSlider(ap, stretchGroup.cs,
- stretchGroup.getName());
+ SliderPanel.setConservationSlider(ap, groupColourScheme, name);
}
- else
+ if (stretchGroup.cs.getThreshold() > 0)
{
- SliderPanel.setPIDSliderSource(ap, stretchGroup.cs,
- stretchGroup.getName());
+ SliderPanel.setPIDSliderSource(ap, groupColourScheme, name);
}
}
PaintRefresher.Refresh(this, av.getSequenceSetId());
- ap.paintAlignment(true);
-
+ // TODO: structure colours only need updating if stretchGroup used to or now
+ // does contain sequences with structure views
+ ap.paintAlignment(updateOverviewAndStructs, updateOverviewAndStructs);
+ updateOverviewAndStructs = false;
changeEndRes = false;
changeStartRes = false;
stretchGroup = null;
*/
public void doMouseDraggedDefineMode(MouseEvent evt)
{
- int res = findRes(evt);
+ int res = findColumn(evt);
int y = findSeq(evt);
if (wrappedBlock != startWrapBlock)
changeStartRes = true;
}
- if (res < av.getStartRes())
+ if (res < av.getRanges().getStartRes())
{
- res = av.getStartRes();
+ res = av.getRanges().getStartRes();
}
if (changeEndRes)
if (res > (stretchGroup.getStartRes() - 1))
{
stretchGroup.setEndRes(res);
+ updateOverviewAndStructs |= av.isSelectionDefinedGroup();
}
}
else if (changeStartRes)
if (res < (stretchGroup.getEndRes() + 1))
{
stretchGroup.setStartRes(res);
+ updateOverviewAndStructs |= av.isSelectionDefinedGroup();
}
}
if (stretchGroup.getSequences(null).contains(nextSeq))
{
stretchGroup.deleteSequence(seq, false);
+ updateOverviewAndStructs |= av.isSelectionDefinedGroup();
}
else
{
}
stretchGroup.addSequence(nextSeq, false);
+ updateOverviewAndStructs |= av.isSelectionDefinedGroup();
}
}
mouseDragging = true;
- if (scrollThread != null)
+ if ((scrollThread != null) && (scrollThread.isRunning()))
{
scrollThread.setEvent(evt);
}
-
- seqCanvas.repaint();
}
void scrollCanvas(MouseEvent evt)
{
if (evt == null)
{
- if (scrollThread != null)
+ if ((scrollThread != null) && (scrollThread.isRunning()))
{
- scrollThread.running = false;
+ scrollThread.stopScrolling();
scrollThread = null;
}
mouseDragging = false;
{
MouseEvent evt;
- boolean running = false;
+ private volatile boolean threadRunning = true;
public ScrollThread()
{
public void stopScrolling()
{
- running = false;
+ threadRunning = false;
+ }
+
+ public boolean isRunning()
+ {
+ return threadRunning;
}
@Override
public void run()
{
- running = true;
-
- while (running)
+ while (threadRunning)
{
if (evt != null)
{
- if (mouseDragging && (evt.getY() < 0) && (av.getStartSeq() > 0))
+ if (mouseDragging && (evt.getY() < 0)
+ && (av.getRanges().getStartSeq() > 0))
{
- running = ap.scrollUp(true);
+ av.getRanges().scrollUp(true);
}
- if (mouseDragging && (evt.getY() >= getHeight())
- && (av.getAlignment().getHeight() > av.getEndSeq()))
+ if (mouseDragging && (evt.getY() >= getHeight()) && (av
+ .getAlignment().getHeight() > av.getRanges().getEndSeq()))
{
- running = ap.scrollUp(false);
+ av.getRanges().scrollUp(false);
}
if (mouseDragging && (evt.getX() < 0))
{
- running = ap.scrollRight(false);
+ av.getRanges().scrollRight(false);
}
else if (mouseDragging && (evt.getX() >= getWidth()))
{
- running = ap.scrollRight(true);
+ av.getRanges().scrollRight(true);
}
}
*/
@Override
public void selection(SequenceGroup seqsel, ColumnSelection colsel,
- SelectionSource source)
+ HiddenColumns hidden, SelectionSource source)
{
// TODO: fix this hack - source of messages is align viewport, but SeqPanel
// handles selection messages...
// TODO: extend config options to allow user to control if selections may be
// shared between viewports.
- boolean iSentTheSelection = (av == source || (source instanceof AlignViewport && ((AlignmentViewport) source)
- .getSequenceSetId().equals(av.getSequenceSetId())));
- if (iSentTheSelection || !av.followSelection)
+ boolean iSentTheSelection = (av == source
+ || (source instanceof AlignViewport
+ && ((AlignmentViewport) source).getSequenceSetId()
+ .equals(av.getSequenceSetId())));
+
+ if (iSentTheSelection)
+ {
+ // respond to our own event by updating dependent dialogs
+ if (ap.getCalculationDialog() != null)
+ {
+ ap.getCalculationDialog().validateCalcTypes();
+ }
+
+ return;
+ }
+
+ // process further ?
+ if (!av.followSelection)
{
return;
}
* Check for selection in a view of which this one is a dna/protein
* complement.
*/
- if (selectionFromTranslation(seqsel, colsel, source))
+ if (selectionFromTranslation(seqsel, colsel, hidden, source))
{
return;
}
{
if (av.getAlignment() == null)
{
- Cache.log.warn("alignviewport av SeqSetId="
- + av.getSequenceSetId() + " ViewId=" + av.getViewId()
+ Cache.log.warn("alignviewport av SeqSetId=" + av.getSequenceSetId()
+ + " ViewId=" + av.getViewId()
+ " 's alignment is NULL! returning immediately.");
return;
}
}
else
{
- av.getColumnSelection().setElementsFrom(colsel);
+ av.getColumnSelection().setElementsFrom(colsel,
+ av.getAlignment().getHiddenColumns());
}
}
av.isColSelChanged(true);
repaint = true;
}
- if (copycolsel
- && av.hasHiddenColumns()
- && (av.getColumnSelection() == null || av.getColumnSelection()
- .getHiddenColumns() == null))
+ if (copycolsel && av.hasHiddenColumns()
+ && (av.getAlignment().getHiddenColumns() == null))
{
System.err.println("Bad things");
}
PaintRefresher.Refresh(this, av.getSequenceSetId());
// ap.paintAlignment(false);
}
+
+ // lastly, update dependent dialogs
+ if (ap.getCalculationDialog() != null)
+ {
+ ap.getCalculationDialog().validateCalcTypes();
+ }
+
}
/**
* @param source
*/
protected boolean selectionFromTranslation(SequenceGroup seqsel,
- ColumnSelection colsel, SelectionSource source)
+ ColumnSelection colsel, HiddenColumns hidden,
+ SelectionSource source)
{
if (!(source instanceof AlignViewportI))
{
/*
* Map column selection
*/
- ColumnSelection cs = MappingUtils.mapColumnSelection(colsel, sourceAv,
- av);
+ // ColumnSelection cs = MappingUtils.mapColumnSelection(colsel, sourceAv,
+ // av);
+ ColumnSelection cs = new ColumnSelection();
+ HiddenColumns hs = new HiddenColumns();
+ MappingUtils.mapColumnSelection(colsel, hidden, sourceAv, av, cs, hs);
av.setColumnSelection(cs);
+ av.getAlignment().setHiddenColumns(hs);
+
+ // lastly, update any dependent dialogs
+ if (ap.getCalculationDialog() != null)
+ {
+ ap.getCalculationDialog().validateCalcTypes();
+ }
PaintRefresher.Refresh(this, av.getSequenceSetId());