X-Git-Url: http://source.jalview.org/gitweb/?a=blobdiff_plain;ds=sidebyside;f=src%2Fjalview%2Fgui%2FSeqPanel.java;h=54e3db731b9b6f6870e60d95276ff8dc18bcd31b;hb=d68d427703088222c2ca6f5968affd5932143eaf;hp=136d22211dea772712395c667e414044d1127fd4;hpb=9442066e24093143605ae849f88e0a11e350da2b;p=jalview.git diff --git a/src/jalview/gui/SeqPanel.java b/src/jalview/gui/SeqPanel.java index 136d222..54e3db7 100644 --- a/src/jalview/gui/SeqPanel.java +++ b/src/jalview/gui/SeqPanel.java @@ -20,20 +20,48 @@ */ package jalview.gui; +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Font; +import java.awt.FontMetrics; +import java.awt.Point; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.MouseEvent; +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 javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JToolTip; +import javax.swing.SwingUtilities; +import javax.swing.Timer; +import javax.swing.ToolTipManager; + import jalview.api.AlignViewportI; -import jalview.bin.Cache; +import jalview.bin.Console; 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; +import jalview.datamodel.MappedFeatures; +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; @@ -45,24 +73,8 @@ import jalview.util.MappingUtils; import jalview.util.MessageManager; import jalview.util.Platform; import jalview.viewmodel.AlignmentViewport; - -import java.awt.BorderLayout; -import java.awt.Color; -import java.awt.Font; -import java.awt.FontMetrics; -import java.awt.Point; -import java.awt.event.MouseEvent; -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.List; - -import javax.swing.JOptionPane; -import javax.swing.JPanel; -import javax.swing.SwingUtilities; -import javax.swing.ToolTipManager; +import jalview.viewmodel.ViewportRanges; +import jalview.viewmodel.seqfeatures.FeatureRendererModel; /** * DOCUMENT ME! @@ -70,20 +82,100 @@ 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; - protected int lastres; + /* + * last position for mouseMoved event + */ + private MousePos lastMousePosition; + + protected int editLastRes; - protected int startseq; + protected int editStartSeq; protected AlignViewport av; @@ -120,50 +212,56 @@ public class SeqPanel extends JPanel implements MouseListener, StringBuffer keyboardNo2; - java.net.URL linkImageURL; - private final SequenceAnnotationReport seqARep; - StringBuffer tooltipText = new StringBuffer(); + /* + * the last tooltip on mousing over the alignment (or annotation in wrapped mode) + * - the tooltip is not set again if unchanged + * - this is the tooltip text _before_ formatting as html + */ + private String lastTooltip; - String tmpString; + /* + * the last tooltip on mousing over the alignment (or annotation in wrapped mode) + * - used to decide where to place the tooltip in getTooltipLocation() + * - this is the tooltip text _after_ formatting as html + */ + private String lastFormattedTooltip; EditCommand editCommand; StructureSelectionManager ssm; - SearchResults lastSearchResults; + 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()); + seqARep = new SequenceAnnotationReport(true); 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); } @@ -174,20 +272,96 @@ 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 + *
+ * Mouse position is not set if in wrapped mode with the cursor either between + * sequences, or over the left or right vertical scale. + * + * @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 + { + ViewportRanges ranges = av.getRanges(); + seqIndex = Math.min((y / charHeight) + ranges.getStartSeq(), + alignmentHeight - 1); + seqIndex = Math.min(seqIndex, ranges.getEndSeq()); + } + + return new MousePos(col, seqIndex, annIndex); + } + + /** * Returns the aligned sequence position (base 0) at the mouse position, or * the closest visible one + *
+ * Returns -1 if in wrapped mode with the mouse over either left or right + * vertical scale. * * @param evt * @return */ - int findRes(MouseEvent evt) + int findColumn(MouseEvent evt) { 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()) { @@ -198,74 +372,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.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.getStartRes(); - if (res > av.getEndRes()) - { - // moused off right - res = av.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.getColumnSelection().adjustForHiddenColumns(res); + res = av.getAlignment().getHiddenColumns() + .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.getStartSeq(), av - .getAlignment().getHeight() - 1); - } - - return seq; } /** @@ -279,16 +429,16 @@ 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 { /* * Tidy up come what may... */ - startseq = -1; - lastres = -1; + editStartSeq = -1; + editLastRes = -1; editingSeqs = false; groupEditing = false; keyboardNo1 = null; @@ -300,13 +450,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() @@ -319,7 +469,7 @@ public class SeqPanel extends JPanel implements MouseListener, { seqCanvas.cursorX = getKeyboardNo1() - 1; seqCanvas.cursorY = getKeyboardNo2() - 1; - scrollToVisible(); + scrollToVisible(true); } } @@ -328,36 +478,99 @@ 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) { - seqCanvas.cursorX += dx; - seqCanvas.cursorY += dy; - if (av.hasHiddenColumns() - && !av.getColumnSelection().isVisible(seqCanvas.cursorX)) + moveCursor(dx, dy, false); + } + + void moveCursor(int dx, int dy, boolean nextWord) + { + HiddenColumns hidden = av.getAlignment().getHiddenColumns(); + + if (nextWord) + { + int maxWidth = av.getAlignment().getWidth(); + int maxHeight = av.getAlignment().getHeight(); + SequenceI seqAtRow = av.getAlignment() + .getSequenceAt(seqCanvas.cursorY); + // look for next gap or residue + boolean isGap = Comparison + .isGap(seqAtRow.getCharAt(seqCanvas.cursorX)); + int p = seqCanvas.cursorX, lastP, r = seqCanvas.cursorY, lastR; + do + { + lastP = p; + lastR = r; + if (dy != 0) + { + r += dy; + if (r < 0) + { + r = 0; + } + if (r >= maxHeight) + { + r = maxHeight - 1; + } + seqAtRow = av.getAlignment().getSequenceAt(r); + } + p = nextVisible(hidden, maxWidth, p, dx); + } while ((dx != 0 ? p != lastP : r != lastR) + && isGap == Comparison.isGap(seqAtRow.getCharAt(p))); + seqCanvas.cursorX = p; + seqCanvas.cursorY = r; + } + else { - int original = seqCanvas.cursorX - dx; int maxWidth = av.getAlignment().getWidth(); + seqCanvas.cursorX = nextVisible(hidden, maxWidth, seqCanvas.cursorX, + dx); + seqCanvas.cursorY += dy; + } + scrollToVisible(false); + } - while (!av.getColumnSelection().isVisible(seqCanvas.cursorX) - && seqCanvas.cursorX < maxWidth && seqCanvas.cursorX > 0) - { - seqCanvas.cursorX += dx; - } + private int nextVisible(HiddenColumns hidden, int maxWidth, int original, + int dx) + { + int newCursorX = original + dx; + if (av.hasHiddenColumns() && !hidden.isVisible(newCursorX)) + { + int visx = hidden.absoluteToVisibleColumn(newCursorX - dx); + int[] region = hidden.getRegionWithEdgeAtRes(visx); - if (seqCanvas.cursorX >= maxWidth - || !av.getColumnSelection().isVisible(seqCanvas.cursorX)) + if (region != null) // just in case { - seqCanvas.cursorX = original; + if (dx == 1) + { + // moving right + newCursorX = region[1] + 1; + } + else if (dx == -1) + { + // moving left + newCursorX = region[0] - 1; + } } } - - scrollToVisible(); + newCursorX = (newCursorX < 0) ? 0 : newCursorX; + if (newCursorX >= maxWidth || !hidden.isVisible(newCursorX)) + { + newCursorX = original; + } + return newCursorX; } - 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) { @@ -378,44 +591,41 @@ public class SeqPanel extends JPanel implements MouseListener, } 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) - { - ap.scrollUp(true); - } - while (seqCanvas.cursorY + 1 > av.endSeq) + if (av.getWrapAlignment()) { - ap.scrollUp(false); + // scrollToWrappedVisible expects x-value to have hidden cols subtracted + int x = av.getAlignment().getHiddenColumns() + .absoluteToVisibleColumn(seqCanvas.cursorX); + av.getRanges().scrollToWrappedVisible(x); } - if (!av.getWrapAlignment()) + else { - 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), - seqCanvas.cursorX, seqCanvas.cursorY); - seqCanvas.repaint(); + if (av.getAlignment().getHiddenColumns().isVisible(seqCanvas.cursorX)) + { + setStatusMessage(av.getAlignment().getSequenceAt(seqCanvas.cursorY), + seqCanvas.cursorX, seqCanvas.cursorY); + } + + if (repaintNeeded) + { + seqCanvas.repaint(); + } } void setSelectionAreaAtCursor(boolean topLeft) @@ -488,15 +698,15 @@ public class SeqPanel extends JPanel implements MouseListener, av.setSelectionGroup(sg); } - ap.paintAlignment(false); + ap.paintAlignment(false, false); av.sendSelection(); } void insertGapAtCursor(boolean group) { groupEditing = group; - startseq = seqCanvas.cursorY; - lastres = seqCanvas.cursorX; + editStartSeq = seqCanvas.cursorY; + editLastRes = seqCanvas.cursorX; editSequence(true, false, seqCanvas.cursorX + getKeyboardNo1()); endEditing(); } @@ -504,8 +714,8 @@ public class SeqPanel extends JPanel implements MouseListener, void deleteGapAtCursor(boolean group) { groupEditing = group; - startseq = seqCanvas.cursorY; - lastres = seqCanvas.cursorX + getKeyboardNo1(); + editStartSeq = seqCanvas.cursorY; + editLastRes = seqCanvas.cursorX + getKeyboardNo1(); editSequence(false, false, seqCanvas.cursorX); endEditing(); } @@ -514,8 +724,8 @@ public class SeqPanel extends JPanel implements MouseListener, { // TODO not called - delete? groupEditing = group; - startseq = seqCanvas.cursorY; - lastres = seqCanvas.cursorX; + editStartSeq = seqCanvas.cursorY; + editLastRes = seqCanvas.cursorX; editSequence(false, true, seqCanvas.cursorX + getKeyboardNo1()); endEditing(); } @@ -580,23 +790,31 @@ 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; } - if (!editingSeqs) + if (editingSeqs) { - doMouseReleasedDefineMode(evt); - return; + endEditing(); + } + else + { + doMouseReleasedDefineMode(evt, didDrag); } - - endEditing(); } /** @@ -609,6 +827,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)) { @@ -616,38 +839,34 @@ public class SeqPanel extends JPanel implements MouseListener, 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 { - doMousePressedDefineMode(evt); + doMousePressedDefineMode(evt, pos); return; } - int seq = findSeq(evt); - int res = findRes(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())) { - startseq = seq; - lastres = res; + editStartSeq = seq; + editLastRes = res; } else { - startseq = -1; - lastres = -1; + editStartSeq = -1; + editLastRes = -1; } return; @@ -675,90 +894,196 @@ public class SeqPanel extends JPanel implements MouseListener, * the start of the highlighted region. */ @Override - public void highlightSequence(SearchResults results) + public String highlightSequence(SearchResultsI results) { if (results == null || results.equals(lastSearchResults)) { - return; + return null; } 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); + if (wasScrolled) { seqCanvas.revalidate(); } + ap.setToScrollComplementPanel(true); } - setStatusMessage(results); - seqCanvas.highlightSearchResults(results); - } - - @Override - public VamsasSource getVamsasSource() - { - return this.ap == null ? null : this.ap.av; - } - @Override - public void updateColours(SequenceI seq, int index) - { - System.out.println("update the seqPanel colours"); - // repaint(); + boolean fastPaint = !(wasScrolled && av.getWrapAlignment()); + if (seqCanvas.highlightSearchResults(results, fastPaint)) + { + setStatusMessage(results); + } + return results.isEmpty() ? null : getHighlightInfo(results); } /** - * DOCUMENT ME! + * temporary hack: answers a message suitable to show on structure hover + * label. This is normally null. It is a peptide variation description if + *
+ * 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 seqName + * @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(String seqName, 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").append(seqno).append(" ID: ") - .append(sequence.getName()); + String seqno = seqIndex == -1 ? "" : " " + (seqIndex + 1); + text.append("Sequence").append(seqno).append(" ID: ").append(seqName); 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; + ap.alignFrame.setStatus(text.toString()); } /** @@ -909,7 +1355,7 @@ public class SeqPanel extends JPanel implements MouseListener, * * @param results */ - private void setStatusMessage(SearchResults results) + private void setStatusMessage(SearchResultsI results) { AlignmentI al = this.av.getAlignment(); int sequenceIndex = al.findIndex(results); @@ -917,8 +1363,9 @@ public class SeqPanel extends JPanel implements MouseListener, { return; } - SequenceI ds = al.getSequenceAt(sequenceIndex).getDatasetSequence(); - for (Match m : results.getResults()) + SequenceI alignedSeq = al.getSequenceAt(sequenceIndex); + SequenceI ds = alignedSeq.getDatasetSequence(); + for (SearchResultMatchI m : results.getResults()) { SequenceI seq = m.getSequence(); if (seq.getDatasetSequence() != null) @@ -928,42 +1375,51 @@ 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(alignedSeq.getName(), sequenceIndex, + seq.getCharAt(start - 1), start); return; } } } /** - * DOCUMENT ME! - * - * @param evt - * DOCUMENT ME! + * {@inheritDoc} */ @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; + 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++; } @@ -972,24 +1428,56 @@ public class SeqPanel extends JPanel implements MouseListener, 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()); @@ -1002,23 +1490,23 @@ public class SeqPanel extends JPanel implements MouseListener, if (!editingSeqs) { - doMouseDraggedDefineMode(evt); + dragStretchGroup(evt); return; } - int res = findRes(evt); + int res = pos.column; if (res < 0) { res = 0; } - if ((lastres == -1) || (lastres == res)) + if ((editLastRes == -1) || (editLastRes == res)) { return; } - if ((res < av.getAlignment().getWidth()) && (res < lastres)) + if ((res < av.getAlignment().getWidth()) && (res < editLastRes)) { // dragLeft, delete gap editSequence(false, false, res); @@ -1031,20 +1519,44 @@ public class SeqPanel extends JPanel implements MouseListener, mouseDragging = true; if (scrollThread != null) { - scrollThread.setEvent(evt); + scrollThread.setMousePosition(evt.getPoint()); } } - // TODO: Make it more clever than many booleans + /** + * Edits the sequence to insert or delete one or more gaps, in response to a + * mouse drag or cursor mode command. The number of inserts/deletes may be + * specified with the cursor command, or else depends on the mouse event + * (normally one column, but potentially more for a fast mouse drag). + *
+ * Delete gaps is limited to the number of gaps left of the cursor position + * (mouse drag), or at or right of the cursor position (cursor mode). + *
+ * In group editing mode (Ctrl or Cmd down), the edit acts on all sequences in + * the current selection group. + *
+ * In locked editing mode (with a selection group present), inserts/deletions
+ * within the selection group are limited to its boundaries (and edits outside
+ * the group stop at its border).
+ *
+ * @param insertGap
+ * true to insert gaps, false to delete gaps
+ * @param editSeq
+ * (unused parameter)
+ * @param startres
+ * the column at which to perform the action; the number of columns
+ * affected depends on this.editLastRes
(cursor column
+ * position)
+ */
synchronized void editSequence(boolean insertGap, boolean editSeq,
- int startres)
+ final int startres)
{
int fixedLeft = -1;
int fixedRight = -1;
boolean fixedColumns = false;
SequenceGroup sg = av.getSelectionGroup();
- SequenceI seq = av.getAlignment().getSequenceAt(startseq);
+ final SequenceI seq = av.getAlignment().getSequenceAt(editStartSeq);
// No group, but the sequence may represent a group
if (!groupEditing && av.hasHiddenRows())
@@ -1056,29 +1568,38 @@ public class SeqPanel extends JPanel implements MouseListener,
}
}
- StringBuilder message = new StringBuilder(64);
+ StringBuilder message = new StringBuilder(64); // for status bar
+
+ /*
+ * make a name for the edit action, for
+ * status bar message and Undo/Redo menu
+ */
+ String label = null;
if (groupEditing)
{
message.append("Edit group:");
- if (editCommand == null)
- {
- editCommand = new EditCommand(
- MessageManager.getString("action.edit_group"));
- }
+ label = MessageManager.getString("action.edit_group");
}
else
{
message.append("Edit sequence: " + seq.getName());
- String label = seq.getName();
+ label = seq.getName();
if (label.length() > 10)
{
label = label.substring(0, 10);
}
- if (editCommand == null)
- {
- editCommand = new EditCommand(MessageManager.formatMessage(
- "label.edit_params", new String[] { label }));
- }
+ label = MessageManager.formatMessage("label.edit_params",
+ new String[]
+ { label });
+ }
+
+ /*
+ * initialise the edit command if there is not
+ * already one being extended
+ */
+ if (editCommand == null)
+ {
+ editCommand = new EditCommand(label);
}
if (insertGap)
@@ -1090,13 +1611,17 @@ public class SeqPanel extends JPanel implements MouseListener,
message.append(" delete ");
}
- message.append(Math.abs(startres - lastres) + " gaps.");
- ap.alignFrame.statusBar.setText(message.toString());
+ message.append(Math.abs(startres - editLastRes) + " gaps.");
+ ap.alignFrame.setStatus(message.toString());
- // Are we editing within a selection group?
- if (groupEditing
- || (sg != null && sg.getSequences(av.getHiddenRepSequences())
- .contains(seq)))
+ /*
+ * is there a selection group containing the sequence being edited?
+ * if so the boundary of the group is the limit of the edit
+ * (but the edit may be inside or outside the selection group)
+ */
+ boolean inSelectionGroup = sg != null
+ && sg.getSequences(av.getHiddenRepSequences()).contains(seq);
+ if (groupEditing || inSelectionGroup)
{
fixedColumns = true;
@@ -1115,10 +1640,10 @@ public class SeqPanel extends JPanel implements MouseListener,
fixedLeft = sg.getStartRes();
fixedRight = sg.getEndRes();
- if ((startres < fixedLeft && lastres >= fixedLeft)
- || (startres >= fixedLeft && lastres < fixedLeft)
- || (startres > fixedRight && lastres <= fixedRight)
- || (startres <= fixedRight && lastres > fixedRight))
+ if ((startres < fixedLeft && editLastRes >= fixedLeft)
+ || (startres >= fixedLeft && editLastRes < fixedLeft)
+ || (startres > fixedRight && editLastRes <= fixedRight)
+ || (startres <= fixedRight && editLastRes > fixedRight))
{
endEditing();
return;
@@ -1139,11 +1664,13 @@ public class SeqPanel extends JPanel implements MouseListener,
if (av.hasHiddenColumns())
{
fixedColumns = true;
- int y1 = av.getColumnSelection().getHiddenBoundaryLeft(startres);
- int y2 = av.getColumnSelection().getHiddenBoundaryRight(startres);
+ int y1 = av.getAlignment().getHiddenColumns()
+ .getNextHiddenBoundary(true, startres);
+ int y2 = av.getAlignment().getHiddenColumns()
+ .getNextHiddenBoundary(false, startres);
- if ((insertGap && startres > y1 && lastres < y1)
- || (!insertGap && startres < y2 && lastres > y2))
+ if ((insertGap && startres > y1 && editLastRes < y1)
+ || (!insertGap && startres < y2 && editLastRes > y2))
{
endEditing();
return;
@@ -1164,6 +1691,54 @@ public class SeqPanel extends JPanel implements MouseListener,
}
}
+ boolean success = doEditSequence(insertGap, editSeq, startres,
+ fixedRight, fixedColumns, sg);
+
+ /*
+ * report what actually happened (might be less than
+ * what was requested), by inspecting the edit commands added
+ */
+ String msg = getEditStatusMessage(editCommand);
+ ap.alignFrame.setStatus(msg == null ? " " : msg);
+ if (!success)
+ {
+ endEditing();
+ }
+
+ editLastRes = startres;
+ seqCanvas.repaint();
+ }
+
+ /**
+ * A helper method that performs the requested editing to insert or delete
+ * gaps (if possible). Answers true if the edit was successful, false if could
+ * only be performed in part or not at all. Failure may occur in 'locked edit'
+ * mode, when an insertion requires a matching gapped position (or column) to
+ * delete, and deletion requires an adjacent gapped position (or column) to
+ * remove.
+ *
+ * @param insertGap
+ * true if inserting gap(s), false if deleting
+ * @param editSeq
+ * (unused parameter, currently always false)
+ * @param startres
+ * the column at which to perform the edit
+ * @param fixedRight
+ * fixed right boundary column of a locked edit (within or to the
+ * left of a selection group)
+ * @param fixedColumns
+ * true if this is a locked edit
+ * @param sg
+ * the sequence group (if group edit is being performed)
+ * @return
+ */
+ protected boolean doEditSequence(final boolean insertGap,
+ final boolean editSeq, final int startres, int fixedRight,
+ final boolean fixedColumns, final SequenceGroup sg)
+ {
+ final SequenceI seq = av.getAlignment().getSequenceAt(editStartSeq);
+ SequenceI[] seqs = new SequenceI[] { seq };
+
if (groupEditing)
{
List
+ *
+ * Answers true if a scroll was performed, false if not - meaning either
+ * that the mouse position is within the panel, or the edge of the alignment
+ * has been reached.
+ */
+ boolean scrollOnce()
+ {
+ /*
+ * quit after mouseUp ensures interrupt in JalviewJS
+ */
+ if (!mouseDragging)
+ {
+ return false;
+ }
+
+ boolean scrolled = false;
+ ViewportRanges ranges = SeqPanel.this.av.getRanges();
+
+ /*
+ * scroll up or down
+ */
+ if (mousePos.y < 0)
+ {
+ // mouse is above this panel - try scroll up
+ scrolled = ranges.scrollUp(true);
+ }
+ else if (mousePos.y >= getHeight())
+ {
+ // mouse is below this panel - try scroll down
+ scrolled = ranges.scrollUp(false);
+ }
+
+ /*
+ * scroll left or right
+ */
+ if (mousePos.x < 0)
+ {
+ scrolled |= ranges.scrollRight(false);
+ }
+ else if (mousePos.x >= getWidth())
+ {
+ scrolled |= ranges.scrollRight(true);
+ }
+ return scrolled;
}
}
@@ -1968,15 +2745,30 @@ public class SeqPanel extends JPanel implements MouseListener,
*/
@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;
}
@@ -1993,7 +2785,7 @@ public class SeqPanel extends JPanel implements MouseListener,
* 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;
}
@@ -2012,8 +2804,8 @@ public class SeqPanel extends JPanel implements MouseListener,
{
if (av.getAlignment() == null)
{
- Cache.log.warn("alignviewport av SeqSetId="
- + av.getSequenceSetId() + " ViewId=" + av.getViewId()
+ Console.warn("alignviewport av SeqSetId=" + av.getSequenceSetId()
+ + " ViewId=" + av.getViewId()
+ " 's alignment is NULL! returning immediately.");
return;
}
@@ -2056,17 +2848,16 @@ public class SeqPanel extends JPanel implements MouseListener,
}
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");
}
@@ -2076,6 +2867,13 @@ public class SeqPanel extends JPanel implements MouseListener,
PaintRefresher.Refresh(this, av.getSequenceSetId());
// ap.paintAlignment(false);
}
+
+ // lastly, update dependent dialogs
+ if (ap.getCalculationDialog() != null)
+ {
+ ap.getCalculationDialog().validateCalcTypes();
+ }
+
}
/**
@@ -2088,7 +2886,8 @@ public class SeqPanel extends JPanel implements MouseListener,
* @param source
*/
protected boolean selectionFromTranslation(SequenceGroup seqsel,
- ColumnSelection colsel, SelectionSource source)
+ ColumnSelection colsel, HiddenColumns hidden,
+ SelectionSource source)
{
if (!(source instanceof AlignViewportI))
{
@@ -2105,18 +2904,41 @@ public class SeqPanel extends JPanel implements MouseListener,
* Map sequence selection
*/
SequenceGroup sg = MappingUtils.mapSequenceGroup(seqsel, sourceAv, av);
- av.setSelectionGroup(sg);
+ av.setSelectionGroup(sg != null && sg.getSize() > 0 ? sg : null);
av.isSelectionGroupChanged(true);
/*
* 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);
+ boolean hiddenChanged = av.getAlignment().setHiddenColumns(hs);
- PaintRefresher.Refresh(this, av.getSequenceSetId());
+ // lastly, update any dependent dialogs
+ if (ap.getCalculationDialog() != null)
+ {
+ ap.getCalculationDialog().validateCalcTypes();
+ }
+
+ /*
+ * repaint alignment, and also Overview or Structure
+ * if hidden column selection has changed
+ */
+ ap.paintAlignment(hiddenChanged, hiddenChanged);
return true;
}
+
+ /**
+ *
+ * @return null or last search results handled by this panel
+ */
+ public SearchResultsI getLastSearchResults()
+ {
+ return lastSearchResults;
+ }
}