X-Git-Url: http://source.jalview.org/gitweb/?a=blobdiff_plain;ds=sidebyside;f=src%2Fjalview%2Fgui%2FSeqPanel.java;h=a53edb9203a4f55080fb11989713db3c24314fc2;hb=fd5f6d3eed59b5f6d9a7cc09e4ddeb7e78eb2f17;hp=ef9d8b3bfe6bd2f669d14222827b30b9f4843ae8;hpb=dec3f30e62e66366d1f697157201c95b50a5e395;p=jalview.git diff --git a/src/jalview/gui/SeqPanel.java b/src/jalview/gui/SeqPanel.java index ef9d8b3..a53edb9 100644 --- a/src/jalview/gui/SeqPanel.java +++ b/src/jalview/gui/SeqPanel.java @@ -25,6 +25,7 @@ import jalview.bin.Cache; import jalview.commands.EditCommand; import jalview.commands.EditCommand.Action; import jalview.commands.EditCommand.Edit; +import jalview.datamodel.AlignmentAnnotation; import jalview.datamodel.AlignmentI; import jalview.datamodel.ColumnSelection; import jalview.datamodel.HiddenColumns; @@ -59,10 +60,8 @@ import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.awt.event.MouseWheelEvent; import java.awt.event.MouseWheelListener; -import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.ListIterator; import javax.swing.JPanel; import javax.swing.SwingUtilities; @@ -74,17 +73,97 @@ 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!! */ + /* + * a class that holds computed mouse position + * - column of the alignment (0...) + * - sequence offset (0...) + * - annotation row offset (0...) + * where annotation offset is -1 unless the alignment is shown + * in wrapped mode, annotations are shown, and the mouse is + * over an annnotation row + */ + static class MousePos + { + /* + * alignment column position of cursor (0...) + */ + final int column; + + /* + * index in alignment of sequence under cursor, + * or nearest above if cursor is not over a sequence + */ + final int seqIndex; + + /* + * index in annotations array of annotation under the cursor + * (only possible in wrapped mode with annotations shown), + * or -1 if cursor is not over an annotation row + */ + final int annotationIndex; + + MousePos(int col, int seq, int ann) + { + column = col; + seqIndex = seq; + annotationIndex = ann; + } + + boolean isOverAnnotation() + { + return annotationIndex != -1; + } + + @Override + public boolean equals(Object obj) + { + if (obj == null || !(obj instanceof MousePos)) + { + return false; + } + MousePos o = (MousePos) obj; + boolean b = (column == o.column && seqIndex == o.seqIndex + && annotationIndex == o.annotationIndex); + // System.out.println(obj + (b ? "= " : "!= ") + this); + return b; + } + + /** + * A simple hashCode that ensures that instances that satisfy equals() have + * the same hashCode + */ + @Override + public int hashCode() + { + return column + seqIndex + annotationIndex; + } + + /** + * toString method for debug output purposes only + */ + @Override + public String toString() + { + return String.format("c%d:s%d:a%d", column, seqIndex, + annotationIndex); + } + } + + private static final int MAX_TOOLTIP_LENGTH = 300; + public SeqCanvas seqCanvas; - /** DOCUMENT ME!! */ public AlignmentPanel ap; + /* + * last position for mouseMoved event + */ + private MousePos lastMousePosition; + protected int lastres; protected int startseq; @@ -139,35 +218,33 @@ public class SeqPanel extends JPanel implements MouseListener, SearchResultsI lastSearchResults; /** - * Creates a new SeqPanel object. + * Creates a new SeqPanel object * - * @param avp - * DOCUMENT ME! - * @param p - * DOCUMENT ME! + * @param viewport + * @param alignPanel */ - public SeqPanel(AlignViewport av, AlignmentPanel ap) + public SeqPanel(AlignViewport viewport, AlignmentPanel alignPanel) { linkImageURL = getClass().getResource("/images/link.gif"); seqARep = new SequenceAnnotationReport(linkImageURL.toString()); ToolTipManager.sharedInstance().registerComponent(this); ToolTipManager.sharedInstance().setInitialDelay(0); ToolTipManager.sharedInstance().setDismissDelay(10000); - this.av = av; + this.av = viewport; setBackground(Color.white); - seqCanvas = new SeqCanvas(ap); + seqCanvas = new SeqCanvas(alignPanel); setLayout(new BorderLayout()); add(seqCanvas, BorderLayout.CENTER); - this.ap = ap; + this.ap = alignPanel; - if (!av.isDataset()) + if (!viewport.isDataset()) { addMouseMotionListener(this); addMouseListener(this); addMouseWheelListener(this); - ssm = av.getStructureSelectionManager(); + ssm = viewport.getStructureSelectionManager(); ssm.addStructureViewerListener(this); ssm.addSelectionListener(this); } @@ -178,6 +255,71 @@ public class SeqPanel extends JPanel implements MouseListener, int wrappedBlock = -1; /** + * Computes the column and sequence row (and possibly annotation row when in + * wrapped mode) for the given mouse position + * + * @param evt + * @return + */ + MousePos findMousePosition(MouseEvent evt) + { + int col = findColumn(evt); + int seqIndex = -1; + int annIndex = -1; + int y = evt.getY(); + + int charHeight = av.getCharHeight(); + int alignmentHeight = av.getAlignment().getHeight(); + if (av.getWrapAlignment()) + { + seqCanvas.calculateWrappedGeometry(seqCanvas.getWidth(), + seqCanvas.getHeight()); + + /* + * yPos modulo height of repeating width + */ + int yOffsetPx = y % seqCanvas.wrappedRepeatHeightPx; + + /* + * height of sequences plus space / scale above, + * plus gap between sequences and annotations + */ + int alignmentHeightPixels = seqCanvas.wrappedSpaceAboveAlignment + + alignmentHeight * charHeight + + SeqCanvas.SEQS_ANNOTATION_GAP; + if (yOffsetPx >= alignmentHeightPixels) + { + /* + * mouse is over annotations; find annotation index, also set + * last sequence above (for backwards compatible behaviour) + */ + AlignmentAnnotation[] anns = av.getAlignment() + .getAlignmentAnnotation(); + int rowOffsetPx = yOffsetPx - alignmentHeightPixels; + annIndex = AnnotationPanel.getRowIndex(rowOffsetPx, anns); + seqIndex = alignmentHeight - 1; + } + else + { + /* + * mouse is over sequence (or the space above sequences) + */ + yOffsetPx -= seqCanvas.wrappedSpaceAboveAlignment; + if (yOffsetPx >= 0) + { + seqIndex = Math.min(yOffsetPx / charHeight, alignmentHeight - 1); + } + } + } + else + { + seqIndex = Math.min((y / charHeight) + av.getRanges().getStartSeq(), + alignmentHeight - 1); + } + + return new MousePos(col, seqIndex, annIndex); + } + /** * Returns the aligned sequence position (base 0) at the mouse position, or * the closest visible one * @@ -189,9 +331,11 @@ public class SeqPanel extends JPanel implements MouseListener, int res = 0; int x = evt.getX(); + final int startRes = av.getRanges().getStartRes(); + final int charWidth = av.getCharWidth(); + if (av.getWrapAlignment()) { - int hgap = av.getCharHeight(); if (av.getScaleAboveWrapped()) { @@ -202,77 +346,50 @@ public class SeqPanel extends JPanel implements MouseListener, + hgap + seqCanvas.getAnnotationHeight(); int y = evt.getY(); - y -= hgap; - x -= seqCanvas.LABEL_WEST; + y = Math.max(0, y - hgap); + x -= seqCanvas.getLabelWidthWest(); + if (x < 0) + { + // mouse is over left scale + return -1; + } int cwidth = seqCanvas.getWrappedCanvasWidth(this.getWidth()); if (cwidth < 1) { return 0; } + if (x >= cwidth * charWidth) + { + // mouse is over right scale + return -1; + } wrappedBlock = y / cHeight; - wrappedBlock += av.getRanges().getStartRes() / cwidth; - - res = wrappedBlock * cwidth + x / av.getCharWidth(); - + wrappedBlock += startRes / cwidth; + // allow for wrapped view scrolled right (possible from Overview) + int startOffset = startRes % cwidth; + res = wrappedBlock * cwidth + startOffset + + Math.min(cwidth - 1, x / charWidth); } else { - if (x > seqCanvas.getX() + seqCanvas.getWidth()) - { - // make sure we calculate relative to visible alignment, rather than - // right-hand gutter - x = seqCanvas.getX() + seqCanvas.getWidth(); - } - res = (x / av.getCharWidth()) + av.getRanges().getStartRes(); - if (res > av.getRanges().getEndRes()) - { - // moused off right - res = av.getRanges().getEndRes(); - } + /* + * make sure we calculate relative to visible alignment, + * rather than right-hand gutter + */ + x = Math.min(x, seqCanvas.getX() + seqCanvas.getWidth()); + res = (x / charWidth) + startRes; + res = Math.min(res, av.getRanges().getEndRes()); } if (av.hasHiddenColumns()) { res = av.getAlignment().getHiddenColumns() - .adjustForHiddenColumns(res); + .visibleToAbsoluteColumn(res); } return res; - - } - - int findSeq(MouseEvent evt) - { - int seq = 0; - int y = evt.getY(); - - if (av.getWrapAlignment()) - { - int hgap = av.getCharHeight(); - if (av.getScaleAboveWrapped()) - { - hgap += av.getCharHeight(); - } - - int cHeight = av.getAlignment().getHeight() * av.getCharHeight() - + hgap + seqCanvas.getAnnotationHeight(); - - y -= hgap; - - seq = Math.min((y % cHeight) / av.getCharHeight(), av.getAlignment() - .getHeight() - 1); - } - else - { - seq = Math.min((y / av.getCharHeight()) - + av.getRanges().getStartSeq(), - av - .getAlignment().getHeight() - 1); - } - - return seq; } /** @@ -286,8 +403,8 @@ public class SeqPanel extends JPanel implements MouseListener, 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 { @@ -307,13 +424,13 @@ public class SeqPanel extends JPanel implements MouseListener, void setCursorRow() { seqCanvas.cursorY = getKeyboardNo1() - 1; - scrollToVisible(); + scrollToVisible(true); } void setCursorColumn() { seqCanvas.cursorX = getKeyboardNo1() - 1; - scrollToVisible(); + scrollToVisible(true); } void setCursorRowAndColumn() @@ -326,7 +443,7 @@ public class SeqPanel extends JPanel implements MouseListener, { seqCanvas.cursorX = getKeyboardNo1() - 1; seqCanvas.cursorY = getKeyboardNo2() - 1; - scrollToVisible(); + scrollToVisible(true); } } @@ -335,7 +452,7 @@ public class SeqPanel extends JPanel implements MouseListener, SequenceI sequence = av.getAlignment().getSequenceAt(seqCanvas.cursorY); seqCanvas.cursorX = sequence.findIndex(getKeyboardNo1()) - 1; - scrollToVisible(); + scrollToVisible(true); } void moveCursor(int dx, int dy) @@ -345,16 +462,30 @@ public class SeqPanel extends JPanel implements MouseListener, HiddenColumns hidden = av.getAlignment().getHiddenColumns(); - if (av.hasHiddenColumns() - && !hidden.isVisible(seqCanvas.cursorX)) + if (av.hasHiddenColumns() && !hidden.isVisible(seqCanvas.cursorX)) { int original = seqCanvas.cursorX - dx; int maxWidth = av.getAlignment().getWidth(); - while (!hidden.isVisible(seqCanvas.cursorX) - && seqCanvas.cursorX < maxWidth && seqCanvas.cursorX > 0) + if (!hidden.isVisible(seqCanvas.cursorX)) { - seqCanvas.cursorX += dx; + int visx = hidden.absoluteToVisibleColumn(seqCanvas.cursorX - dx); + int[] region = hidden.getRegionWithEdgeAtRes(visx); + + if (region != null) // just in case + { + if (dx == 1) + { + // moving right + seqCanvas.cursorX = region[1] + 1; + } + else if (dx == -1) + { + // moving left + seqCanvas.cursorX = region[0] - 1; + } + } + seqCanvas.cursorX = (seqCanvas.cursorX < 0) ? 0 : seqCanvas.cursorX; } if (seqCanvas.cursorX >= maxWidth @@ -364,10 +495,16 @@ public class SeqPanel extends JPanel implements MouseListener, } } - 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) { @@ -388,21 +525,44 @@ public class SeqPanel extends JPanel implements MouseListener, } endEditing(); - if (av.getWrapAlignment()) + + boolean repaintNeeded = true; + if (jump) { - av.getRanges().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 { - av.getRanges().scrollToVisible(seqCanvas.cursorX, seqCanvas.cursorY, - av); + if (av.getWrapAlignment()) + { + // scrollToWrappedVisible expects x-value to have hidden cols subtracted + int x = av.getAlignment().getHiddenColumns() + .absoluteToVisibleColumn(seqCanvas.cursorX); + av.getRanges().scrollToWrappedVisible(x); + } + else + { + 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); @@ -473,7 +633,7 @@ public class SeqPanel extends JPanel implements MouseListener, av.setSelectionGroup(sg); } - ap.paintAlignment(false); + ap.paintAlignment(false, false); av.sendSelection(); } @@ -565,13 +725,19 @@ public class SeqPanel extends JPanel implements MouseListener, @Override public void mouseReleased(MouseEvent evt) { + MousePos pos = findMousePosition(evt); + if (pos.isOverAnnotation() || pos.seqIndex == -1 || pos.column == -1) + { + return; + } + boolean didDrag = mouseDragging; // did we come here after a drag mouseDragging = false; mouseWheelPressed = false; if (evt.isPopupTrigger()) // Windows: mouseReleased { - showPopupMenu(evt); + showPopupMenu(evt, pos); evt.consume(); return; } @@ -595,6 +761,11 @@ public class SeqPanel extends JPanel implements MouseListener, public void mousePressed(MouseEvent evt) { lastMousePress = evt.getPoint(); + MousePos pos = findMousePosition(evt); + if (pos.isOverAnnotation() || pos.seqIndex == -1 || pos.column == -1) + { + return; + } if (SwingUtilities.isMiddleMouseButton(evt)) { @@ -613,17 +784,12 @@ public class SeqPanel extends JPanel implements MouseListener, } else { - doMousePressedDefineMode(evt); + doMousePressedDefineMode(evt, pos); return; } - int seq = findSeq(evt); - int res = findColumn(evt); - - if (seq < 0 || res < 0) - { - return; - } + int seq = pos.seqIndex; + int res = pos.column; if ((seq < av.getAlignment().getHeight()) && (res < av.getAlignment().getSequenceAt(seq).getLength())) @@ -670,6 +836,8 @@ public class SeqPanel extends JPanel implements MouseListener, } lastSearchResults = results; + boolean wasScrolled = false; + if (av.isFollowHighlight()) { // don't allow highlight of protein/cDNA to also scroll a complementary @@ -677,14 +845,19 @@ public class SeqPanel extends JPanel implements MouseListener, // over residue to change abruptly, causing highlighted residue in panel 2 // to change, causing a scroll in panel 1 etc) ap.setToScrollComplementPanel(false); - if (ap.scrollToPosition(results, false)) + wasScrolled = ap.scrollToPosition(results, false); + if (wasScrolled) { seqCanvas.revalidate(); } ap.setToScrollComplementPanel(true); } - setStatusMessage(results); - seqCanvas.highlightSearchResults(results); + + boolean noFastPaint = wasScrolled && av.getWrapAlignment(); + if (seqCanvas.highlightSearchResults(results, noFastPaint)) + { + setStatusMessage(results); + } } @Override @@ -701,10 +874,12 @@ public class SeqPanel extends JPanel implements MouseListener, } /** - * 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) @@ -716,10 +891,30 @@ public class SeqPanel extends JPanel implements MouseListener, mouseDragged(evt); } - final int column = findColumn(evt); - int seq = findSeq(evt); + final MousePos mousePos = findMousePosition(evt); + if (mousePos.equals(lastMousePosition)) + { + /* + * just a pixel move without change of 'cell' + */ + return; + } + lastMousePosition = mousePos; + + if (mousePos.isOverAnnotation()) + { + mouseMovedOverAnnotation(mousePos); + return; + } + final int seq = mousePos.seqIndex; + + final int column = mousePos.column; if (column < 0 || seq < 0 || seq >= av.getAlignment().getHeight()) { + lastMousePosition = null; + setToolTipText(null); + lastTooltip = null; + ap.alignFrame.setStatus(""); return; } @@ -772,13 +967,9 @@ public class SeqPanel extends JPanel implements MouseListener, if (av.isShowSequenceFeatures()) { List features = ap.getFeatureRenderer() - .findFeaturesAtRes(sequence.getDatasetSequence(), pos); - if (isGapped) - { - removeAdjacentFeatures(features, column + 1, sequence); - } + .findFeaturesAtColumn(sequence, column + 1); seqARep.appendFeatures(tooltipText, pos, features, - this.ap.getSeqPanel().seqCanvas.fr.getMinMax()); + this.ap.getSeqPanel().seqCanvas.fr); } if (tooltipText.length() == 6) // { @@ -787,47 +978,49 @@ public class SeqPanel extends JPanel implements MouseListener, } else { - if (lastTooltip == null - || !lastTooltip.equals(tooltipText.toString())) + if (tooltipText.length() > MAX_TOOLTIP_LENGTH) // constant { - String formatedTooltipText = JvSwingUtils.wrapTooltip(true, - tooltipText.toString()); - // String formatedTooltipText = tooltipText.toString(); - setToolTipText(formatedTooltipText); - lastTooltip = tooltipText.toString(); + tooltipText.setLength(MAX_TOOLTIP_LENGTH); + tooltipText.append("..."); + } + String textString = tooltipText.toString(); + if (lastTooltip == null || !lastTooltip.equals(textString)) + { + String formattedTooltipText = JvSwingUtils.wrapTooltip(true, + textString); + setToolTipText(formattedTooltipText); + lastTooltip = textString; } - } - } /** - * Removes from the list of features any that start after, or end before, the - * given column position. This allows us to retain only those features - * adjacent to a gapped position that straddle the position. Contact features - * that 'straddle' the position are also removed, since they are not 'at' the - * position. + * When the view is in wrapped mode, and the mouse is over an annotation row, + * shows the corresponding tooltip and status message (if any) * - * @param features + * @param pos * @param column - * alignment column (1..) - * @param sequence */ - protected void removeAdjacentFeatures(List features, - final int column, SequenceI sequence) + protected void mouseMovedOverAnnotation(MousePos pos) { - // TODO should this be an AlignViewController method (and reused by applet)? - ListIterator it = features.listIterator(); - while (it.hasNext()) + final int column = pos.column; + final int rowIndex = pos.annotationIndex; + + if (column < 0 || !av.getWrapAlignment() || !av.isShowAnnotation() + || rowIndex < 0) { - SequenceFeature sf = it.next(); - if (sf.isContactFeature() - || sequence.findIndex(sf.getBegin()) > column - || sequence.findIndex(sf.getEnd()) < column) - { - it.remove(); - } + return; } + AlignmentAnnotation[] anns = av.getAlignment().getAlignmentAnnotation(); + + String tooltip = AnnotationPanel.buildToolTip(anns[rowIndex], column, + anns); + setToolTipText(tooltip); + lastTooltip = tooltip; + + String msg = AnnotationPanel.getStatusMessage(av.getAlignment(), column, + anns[rowIndex]); + ap.alignFrame.setStatus(msg); } private Point lastp = null; @@ -840,30 +1033,38 @@ public class SeqPanel extends JPanel implements MouseListener, @Override public Point getToolTipLocation(MouseEvent event) { - int x = event.getX(), w = getWidth(); - int wdth = (w - x < 200) ? -(w / 2) : 5; // switch sides when tooltip is too - // close to edge + if (tooltipText == null || tooltipText.length() <= 6) + { + lastp = null; + return null; + } + + int x = event.getX(); + int w = getWidth(); + // switch sides when tooltip is too close to edge + int wdth = (w - x < 200) ? -(w / 2) : 5; Point p = lastp; if (!event.isShiftDown() || p == null) { - p = (tooltipText != null && tooltipText.length() > 6) ? new Point( - event.getX() + wdth, event.getY() - 20) : null; + p = new Point(event.getX() + wdth, event.getY() - 20); + lastp = p; } /* - * TODO: try to modify position region is not obcured by tooltip + * TODO: try to set position so region is not obscured by tooltip */ - return lastp = p; + return p; } String lastTooltip; /** * set when the current UI interaction has resulted in a change that requires - * overview shading to be recalculated. this could be changed to something - * more expressive that indicates what actually has changed, so selective - * redraws can be applied + * 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 needOverviewUpdate = false; // TODO: refactor to avcontroller + private boolean updateOverviewAndStructs = false; // TODO: refactor to avcontroller /** * set if av.getSelectionGroup() refers to a group that is defined on the @@ -883,19 +1084,48 @@ public class SeqPanel extends JPanel implements MouseListener, * aligned sequence object * @param column * alignment column - * @param seq + * @param seqIndex * index of sequence in alignment * @return sequence position of residue at column, or adjacent residue if at a * gap */ - int setStatusMessage(SequenceI sequence, final int column, int seq) + 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 + * + *
+   * 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)
+   * 
+ * + * @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) + */ + 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); + String seqno = seqIndex == -1 ? "" : " " + (seqIndex + 1); text.append("Sequence").append(seqno).append(" ID: ") .append(sequence.getName()); @@ -904,31 +1134,28 @@ public class SeqPanel extends JPanel implements MouseListener, /* * Try to translate the display character to residue name (null for gap). */ - final String displayChar = String.valueOf(sequence.getCharAt(column)); - boolean isGapped = Comparison.isGap(sequence.getCharAt(column)); - int pos = sequence.findPosition(column); + boolean isGapped = Comparison.isGap(sequenceChar); if (!isGapped) { boolean nucleotide = av.getAlignment().isNucleotide(); + String displayChar = String.valueOf(sequenceChar); if (nucleotide) { residue = ResidueProperties.nucleotideName.get(displayChar); } else { - residue = "X".equalsIgnoreCase(displayChar) ? "X" : ("*" - .equals(displayChar) ? "STOP" - : ResidueProperties.aa2Triplet.get(displayChar)); + residue = "X".equalsIgnoreCase(displayChar) ? "X" + : ("*".equals(displayChar) ? "STOP" + : ResidueProperties.aa2Triplet.get(displayChar)); } text.append(" ").append(nucleotide ? "Nucleotide" : "Residue") .append(": ").append(residue == null ? displayChar : residue); - text.append(" (").append(Integer.toString(pos)).append(")"); + text.append(" (").append(Integer.toString(residuePos)).append(")"); } - ap.alignFrame.statusBar.setText(text.toString()); - - return pos; + ap.alignFrame.setStatus(text.toString()); } /** @@ -956,12 +1183,9 @@ public class SeqPanel extends JPanel implements MouseListener, 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; } } @@ -973,6 +1197,12 @@ public class SeqPanel extends JPanel implements MouseListener, @Override public void mouseDragged(MouseEvent evt) { + MousePos pos = findMousePosition(evt); + if (pos.isOverAnnotation() || pos.column == -1) + { + return; + } + if (mouseWheelPressed) { boolean inSplitFrame = ap.av.getCodingComplement() != null; @@ -981,8 +1211,8 @@ public class SeqPanel extends JPanel implements MouseListener, 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 @@ -1041,7 +1271,7 @@ public class SeqPanel extends JPanel implements MouseListener, } if (newWidth > 0) { - ap.paintAlignment(false); + ap.paintAlignment(false, false); if (copyChanges) { /* @@ -1068,11 +1298,11 @@ public class SeqPanel extends JPanel implements MouseListener, if (!editingSeqs) { - doMouseDraggedDefineMode(evt); + dragStretchGroup(evt); return; } - int res = findColumn(evt); + int res = pos.column; if (res < 0) { @@ -1095,7 +1325,7 @@ public class SeqPanel extends JPanel implements MouseListener, } mouseDragging = true; - if (scrollThread != null) + if ((scrollThread != null) && (scrollThread.isRunning())) { scrollThread.setEvent(evt); } @@ -1142,8 +1372,9 @@ public class SeqPanel extends JPanel implements MouseListener, } 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 })); } } @@ -1157,12 +1388,11 @@ public class SeqPanel extends JPanel implements MouseListener, } message.append(Math.abs(startres - lastres) + " gaps."); - ap.alignFrame.statusBar.setText(message.toString()); + ap.alignFrame.setStatus(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; @@ -1206,9 +1436,9 @@ public class SeqPanel extends JPanel implements MouseListener, { fixedColumns = true; int y1 = av.getAlignment().getHiddenColumns() - .getHiddenBoundaryLeft(startres); + .getNextHiddenBoundary(true, startres); int y2 = av.getAlignment().getHiddenColumns() - .getHiddenBoundaryRight(startres); + .getNextHiddenBoundary(false, startres); if ((insertGap && startres > y1 && lastres < y1) || (!insertGap && startres < y2 && lastres > y2)) @@ -1258,7 +1488,7 @@ public class SeqPanel extends JPanel implements MouseListener, // 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; @@ -1284,7 +1514,8 @@ public class SeqPanel extends JPanel implements MouseListener, if (sg.getSize() == av.getAlignment().getHeight()) { if ((av.hasHiddenColumns() && startres < av.getAlignment() - .getHiddenColumns().getHiddenBoundaryRight(startres))) + .getHiddenColumns() + .getNextHiddenBoundary(false, startres))) { endEditing(); return; @@ -1350,8 +1581,8 @@ public class SeqPanel extends JPanel implements MouseListener, } else { - appendEdit(Action.INSERT_GAP, groupSeqs, startres, startres - - lastres); + appendEdit(Action.INSERT_GAP, groupSeqs, startres, + startres - lastres); } } else @@ -1366,8 +1597,8 @@ public class SeqPanel extends JPanel implements MouseListener, } else { - appendEdit(Action.DELETE_GAP, groupSeqs, startres, lastres - - startres); + appendEdit(Action.DELETE_GAP, groupSeqs, startres, + lastres - startres); } } @@ -1521,9 +1752,9 @@ public class SeqPanel extends JPanel implements MouseListener, oldSeq = 0; } - if (scrollThread != null) + if ((scrollThread != null) && (scrollThread.isRunning())) { - scrollThread.running = false; + scrollThread.stopScrolling(); scrollThread = null; } } @@ -1537,12 +1768,13 @@ public class SeqPanel extends JPanel implements MouseListener, @Override public void mouseExited(MouseEvent e) { + ap.alignFrame.setStatus(" "); if (av.getWrapAlignment()) { return; } - if (mouseDragging) + if (mouseDragging && scrollThread == null) { scrollThread = new ScrollThread(); } @@ -1557,7 +1789,12 @@ public class SeqPanel extends JPanel implements MouseListener, public void mouseClicked(MouseEvent evt) { SequenceGroup sg = null; - SequenceI sequence = av.getAlignment().getSequenceAt(findSeq(evt)); + MousePos pos = findMousePosition(evt); + if (pos.isOverAnnotation() || pos.seqIndex == -1 || pos.column == -1) + { + return; + } + if (evt.getClickCount() > 1) { sg = av.getSelectionGroup(); @@ -1567,20 +1804,15 @@ public class SeqPanel extends JPanel implements MouseListener, av.setSelectionGroup(null); } - int column = findColumn(evt); - boolean isGapped = Comparison.isGap(sequence.getCharAt(column)); + int column = pos.column; /* * find features at the position (if not gapped), or straddling * the position (if at a gap) */ + SequenceI sequence = av.getAlignment().getSequenceAt(pos.seqIndex); List features = seqCanvas.getFeatureRenderer() - .findFeaturesAtRes(sequence.getDatasetSequence(), - sequence.findPosition(column)); - if (isGapped) - { - removeAdjacentFeatures(features, column, sequence); - } + .findFeaturesAtColumn(sequence, column + 1); if (!features.isEmpty()) { @@ -1590,7 +1822,7 @@ public class SeqPanel extends JPanel implements MouseListener, SearchResultsI highlight = new SearchResults(); highlight.addResult(sequence, features.get(0).getBegin(), features .get(0).getEnd()); - seqCanvas.highlightSearchResults(highlight); + seqCanvas.highlightSearchResults(highlight, false); /* * open the Amend Features dialog; clear highlighting afterwards, @@ -1599,7 +1831,8 @@ public class SeqPanel extends JPanel implements MouseListener, List seqs = Collections.singletonList(sequence); seqCanvas.getFeatureRenderer().amendFeatures(seqs, features, false, ap); - seqCanvas.highlightSearchResults(null); + av.setSearchResults(null); // clear highlighting + seqCanvas.repaint(); // draw new/amended features } } } @@ -1608,7 +1841,8 @@ public class SeqPanel extends JPanel implements MouseListener, public void mouseWheelMoved(MouseWheelEvent e) { e.consume(); - if (e.getWheelRotation() > 0) + double wheelRotation = e.getPreciseWheelRotation(); + if (wheelRotation > 0) { if (e.isShiftDown()) { @@ -1620,7 +1854,7 @@ public class SeqPanel extends JPanel implements MouseListener, av.getRanges().scrollUp(false); } } - else + else if (wheelRotation < 0) { if (e.isShiftDown()) { @@ -1631,37 +1865,34 @@ public class SeqPanel extends JPanel implements MouseListener, 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); } /** * DOCUMENT ME! * - * @param evt + * @param pos * DOCUMENT ME! */ - public void doMousePressedDefineMode(MouseEvent evt) + protected void doMousePressedDefineMode(MouseEvent evt, MousePos pos) { - final int res = findColumn(evt); - final int seq = findSeq(evt); - oldSeq = seq; - needOverviewUpdate = false; - - startWrapBlock = wrappedBlock; - - if (av.getWrapAlignment() && seq > av.getAlignment().getHeight()) + if (pos.isOverAnnotation() || pos.seqIndex == -1 || pos.column == -1) { - JvOptionPane.showInternalMessageDialog(Desktop.desktop, MessageManager - .getString("label.cannot_edit_annotations_in_wrapped_view"), - MessageManager.getString("label.wrapped_view_no_edit"), - JvOptionPane.WARNING_MESSAGE); return; } - if (seq < 0 || res < 0) - { - return; - } + final int res = pos.column; + final int seq = pos.seqIndex; + oldSeq = seq; + updateOverviewAndStructs = false; + + startWrapBlock = wrappedBlock; SequenceI sequence = av.getAlignment().getSequenceAt(seq); @@ -1685,7 +1916,7 @@ public class SeqPanel extends JPanel implements MouseListener, if (evt.isPopupTrigger()) // Mac: mousePressed { - showPopupMenu(evt); + showPopupMenu(evt, pos); return; } @@ -1701,81 +1932,78 @@ public class SeqPanel extends JPanel implements MouseListener, if (av.cursorMode) { - seqCanvas.cursorX = findColumn(evt); - seqCanvas.cursorY = findSeq(evt); + seqCanvas.cursorX = res; + seqCanvas.cursorY = seq; 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); - stretchGroup = sg; + if (stretchGroup != null) + { + stretchGroup.addPropertyChangeListener(seqCanvas); + } - if (av.getConservationSelected()) - { - SliderPanel.setConservationSlider(ap, av.getResidueShading(), - ap.getViewName()); - } + seqCanvas.repaint(); + } - if (av.getAbovePIDThreshold()) - { - SliderPanel.setPIDSliderSource(ap, av.getResidueShading(), - ap.getViewName()); - } - // TODO: stretchGroup will always be not null. Is this a merge error ? - 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(); + 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.getConservationSelected()) + { + SliderPanel.setConservationSlider(ap, av.getResidueShading(), + ap.getViewName()); } - seqCanvas.repaint(); + 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 + * @param pos */ - void showPopupMenu(MouseEvent evt) + void showPopupMenu(MouseEvent evt, MousePos pos) { - final int res = findColumn(evt); - final int seq = findSeq(evt); + final int column = pos.column; + final int seq = pos.seqIndex; SequenceI sequence = av.getAlignment().getSequenceAt(seq); - List allFeatures = ap.getFeatureRenderer() - .findFeaturesAtRes(sequence.getDatasetSequence(), - sequence.findPosition(res)); - List links = new ArrayList<>(); - for (SequenceFeature sf : allFeatures) - { - if (sf.links != null) - { - for (String link : sf.links) - { - links.add(link); - } - } - } + List features = ap.getFeatureRenderer() + .findFeaturesAtColumn(sequence, column + 1); - PopupMenu pop = new PopupMenu(ap, null, links); + PopupMenu pop = new PopupMenu(ap, null, features); pop.show(this, evt.getX(), evt.getY()); } @@ -1788,23 +2016,28 @@ public class SeqPanel extends JPanel implements MouseListener, * true if this event is happening after a mouse drag (rather than a * mouse down) */ - public void doMouseReleasedDefineMode(MouseEvent evt, boolean afterDrag) + protected void doMouseReleasedDefineMode(MouseEvent evt, + boolean afterDrag) { if (stretchGroup == null) { return; } + + 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); - needOverviewUpdate |= vischange && av.isSelectionDefinedGroup() + updateOverviewAndStructs |= vischange && av.isSelectionDefinedGroup() && afterDrag; if (stretchGroup.cs != null) { stretchGroup.cs.alignmentChanged(stretchGroup, av.getHiddenRepSequences()); - ResidueShaderI groupColourScheme = stretchGroup.getGroupColourScheme(); + ResidueShaderI groupColourScheme = stretchGroup + .getGroupColourScheme(); String name = stretchGroup.getName(); if (stretchGroup.cs.conservationApplied()) { @@ -1816,8 +2049,10 @@ public class SeqPanel extends JPanel implements MouseListener, } } PaintRefresher.Refresh(this, av.getSequenceSetId()); - ap.paintAlignment(needOverviewUpdate); - needOverviewUpdate = false; + // 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; @@ -1825,22 +2060,28 @@ public class SeqPanel extends JPanel implements MouseListener, } /** - * DOCUMENT ME! + * Resizes the borders of a selection group depending on the direction of + * mouse drag * * @param evt - * DOCUMENT ME! */ - public void doMouseDraggedDefineMode(MouseEvent evt) + protected void dragStretchGroup(MouseEvent evt) { - int res = findColumn(evt); - int y = findSeq(evt); + if (stretchGroup == null) + { + return; + } - if (wrappedBlock != startWrapBlock) + MousePos pos = findMousePosition(evt); + if (pos.isOverAnnotation() || pos.column == -1 || pos.seqIndex == -1) { return; } - if (stretchGroup == null) + int res = pos.column; + int y = pos.seqIndex; + + if (wrappedBlock != startWrapBlock) { return; } @@ -1871,7 +2112,7 @@ public class SeqPanel extends JPanel implements MouseListener, if (res > (stretchGroup.getStartRes() - 1)) { stretchGroup.setEndRes(res); - needOverviewUpdate |= av.isSelectionDefinedGroup(); + updateOverviewAndStructs |= av.isSelectionDefinedGroup(); } } else if (changeStartRes) @@ -1879,7 +2120,7 @@ public class SeqPanel extends JPanel implements MouseListener, if (res < (stretchGroup.getEndRes() + 1)) { stretchGroup.setStartRes(res); - needOverviewUpdate |= av.isSelectionDefinedGroup(); + updateOverviewAndStructs |= av.isSelectionDefinedGroup(); } } @@ -1913,7 +2154,7 @@ public class SeqPanel extends JPanel implements MouseListener, if (stretchGroup.getSequences(null).contains(nextSeq)) { stretchGroup.deleteSequence(seq, false); - needOverviewUpdate |= av.isSelectionDefinedGroup(); + updateOverviewAndStructs |= av.isSelectionDefinedGroup(); } else { @@ -1923,7 +2164,7 @@ public class SeqPanel extends JPanel implements MouseListener, } stretchGroup.addSequence(nextSeq, false); - needOverviewUpdate |= av.isSelectionDefinedGroup(); + updateOverviewAndStructs |= av.isSelectionDefinedGroup(); } } @@ -1934,21 +2175,19 @@ public class SeqPanel extends JPanel implements MouseListener, 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; @@ -1971,7 +2210,7 @@ public class SeqPanel extends JPanel implements MouseListener, { MouseEvent evt; - boolean running = false; + private volatile boolean threadRunning = true; public ScrollThread() { @@ -1985,38 +2224,40 @@ public class SeqPanel extends JPanel implements MouseListener, 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.getRanges().getStartSeq() > 0)) { - running = av.getRanges().scrollUp(true); + av.getRanges().scrollUp(true); } - if (mouseDragging && (evt.getY() >= getHeight()) - && (av.getAlignment().getHeight() > av.getRanges() - .getEndSeq())) + if (mouseDragging && (evt.getY() >= getHeight()) && (av + .getAlignment().getHeight() > av.getRanges().getEndSeq())) { - running = av.getRanges().scrollUp(false); + av.getRanges().scrollUp(false); } if (mouseDragging && (evt.getX() < 0)) { - running = av.getRanges().scrollRight(false); + av.getRanges().scrollRight(false); } else if (mouseDragging && (evt.getX() >= getWidth())) { - running = av.getRanges().scrollRight(true); + av.getRanges().scrollRight(true); } } @@ -2041,8 +2282,10 @@ public class SeqPanel extends JPanel implements MouseListener, // 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()))); + boolean iSentTheSelection = (av == source + || (source instanceof AlignViewport + && ((AlignmentViewport) source).getSequenceSetId() + .equals(av.getSequenceSetId()))); if (iSentTheSelection) { @@ -2144,10 +2387,8 @@ public class SeqPanel extends JPanel implements MouseListener, repaint = true; } - if (copycolsel - && av.hasHiddenColumns() - && (av.getAlignment().getHiddenColumns() == null || av - .getAlignment().getHiddenColumns().getHiddenRegions() == null)) + if (copycolsel && av.hasHiddenColumns() + && (av.getAlignment().getHiddenColumns() == null)) { System.err.println("Bad things"); } @@ -2218,4 +2459,13 @@ public class SeqPanel extends JPanel implements MouseListener, return true; } + + /** + * + * @return null or last search results handled by this panel + */ + public SearchResultsI getLastSearchResults() + { + return lastSearchResults; + } }