From c735d0c34c8315c3bd86a7f6bf3c28a21c69b1fb Mon Sep 17 00:00:00 2001 From: gmungoc Date: Mon, 1 Oct 2018 12:05:31 +0100 Subject: [PATCH] JAL-3093 show annotation tooltips and status message in wrapped mode --- src/jalview/gui/AnnotationPanel.java | 96 ++++++++------ src/jalview/gui/SeqPanel.java | 235 +++++++++++++++++++++++++++++----- 2 files changed, 265 insertions(+), 66 deletions(-) diff --git a/src/jalview/gui/AnnotationPanel.java b/src/jalview/gui/AnnotationPanel.java index 50971c7..ce37d7e 100755 --- a/src/jalview/gui/AnnotationPanel.java +++ b/src/jalview/gui/AnnotationPanel.java @@ -21,6 +21,7 @@ package jalview.gui; import jalview.datamodel.AlignmentAnnotation; +import jalview.datamodel.AlignmentI; import jalview.datamodel.Annotation; import jalview.datamodel.ColumnSelection; import jalview.datamodel.HiddenColumns; @@ -690,30 +691,10 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI, @Override public void mouseMoved(MouseEvent evt) { + int yPos = evt.getY(); AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation(); - if (aa == null) - { - this.setToolTipText(null); - return; - } - - int row = -1; - int height = 0; - - for (int i = 0; i < aa.length; i++) - { - if (aa[i].visible) - { - height += aa[i].height; - } - - if (evt.getY() < height) - { - row = i; - break; - } - } + int row = getRowIndex(yPos, aa); if (row == -1) { @@ -734,8 +715,9 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI, if (row > -1 && ann.annotations != null && column < ann.annotations.length) { - buildToolTip(ann, column, aa); - setStatusMessage(column, ann); + setToolTipText(buildToolTip(ann, column, aa)); + String msg = getStatusMessage(av.getAlignment(), column, ann); + ap.alignFrame.statusBar.setText(msg); } else { @@ -745,15 +727,51 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI, } /** - * Builds a tooltip for the annotation at the current mouse position. + * Answers the index in the annotations array of the visible annotation at the + * given y position. This is done by adding the heights of visible annotations + * until the y position has been exceeded. Answers -1 if no annotations are + * visible, or the y position is below all annotations. + * + * @param yPos + * @param aa + * @return + */ + static int getRowIndex(int yPos, AlignmentAnnotation[] aa) + { + if (aa == null) + { + return -1; + } + int row = -1; + int height = 0; + + for (int i = 0; i < aa.length; i++) + { + if (aa[i].visible) + { + height += aa[i].height; + } + + if (height > yPos) + { + row = i; + break; + } + } + return row; + } + + /** + * Answers a tooltip for the annotation at the current mouse position * * @param ann * @param column * @param anns */ - void buildToolTip(AlignmentAnnotation ann, int column, + static String buildToolTip(AlignmentAnnotation ann, int column, AlignmentAnnotation[] anns) { + String tooltip = null; if (ann.graphGroup > -1) { StringBuilder tip = new StringBuilder(32); @@ -775,35 +793,39 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI, if (tip.length() != 6) { tip.setLength(tip.length() - 4); - this.setToolTipText(tip.toString() + ""); + tooltip = tip.toString() + ""; } } - else if (ann.annotations[column] != null) + else if (column < ann.annotations.length + && ann.annotations[column] != null) { String description = ann.annotations[column].description; if (description != null && description.length() > 0) { - this.setToolTipText(JvSwingUtils.wrapTooltip(true, description)); + tooltip = JvSwingUtils.wrapTooltip(true, description); } else { - this.setToolTipText(null); // no tooltip if null or empty description + tooltip = null; // no tooltip if null or empty description } } else { // clear the tooltip. - this.setToolTipText(null); + tooltip = null; } + return tooltip; } /** - * Constructs and displays the status bar message + * Constructs and returns the status bar message * + * @param al * @param column * @param ann */ - void setStatusMessage(int column, AlignmentAnnotation ann) + static String getStatusMessage(AlignmentI al, int column, + AlignmentAnnotation ann) { /* * show alignment column and annotation description if any @@ -812,7 +834,7 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI, text.append(MessageManager.getString("label.column")).append(" ") .append(column + 1); - if (ann.annotations[column] != null) + if (column < ann.annotations.length && ann.annotations[column] != null) { String description = ann.annotations[column].description; if (description != null && description.trim().length() > 0) @@ -828,7 +850,7 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI, SequenceI seqref = ann.sequenceRef; if (seqref != null) { - int seqIndex = av.getAlignment().findIndex(seqref); + int seqIndex = al.findIndex(seqref); if (seqIndex != -1) { text.append(", ").append(MessageManager.getString("label.sequence")) @@ -838,7 +860,7 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI, { text.append(" "); String name; - if (av.getAlignment().isNucleotide()) + if (al.isNucleotide()) { name = ResidueProperties.nucleotideName .get(String.valueOf(residue)); @@ -859,7 +881,7 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI, } } - ap.alignFrame.statusBar.setText(text.toString()); + return text.toString(); } /** diff --git a/src/jalview/gui/SeqPanel.java b/src/jalview/gui/SeqPanel.java index 8b2e7bc..14ec6e9 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; @@ -76,6 +77,77 @@ public class SeqPanel extends JPanel implements MouseListener, MouseMotionListener, MouseWheelListener, SequenceListener, SelectionListener { + /* + * 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; + } + + @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; @@ -83,14 +155,9 @@ public class SeqPanel extends JPanel public AlignmentPanel ap; /* - * last column position for mouseMoved event + * last position for mouseMoved event */ - private int lastMouseColumn; - - /* - * last sequence offset for mouseMoved event - */ - private int lastMouseSeq; + private MousePos lastMousePosition; protected int lastres; @@ -176,9 +243,6 @@ public class SeqPanel extends JPanel ssm.addStructureViewerListener(this); ssm.addSelectionListener(this); } - - lastMouseColumn = -1; - lastMouseSeq = -1; } int startWrapBlock = -1; @@ -186,6 +250,66 @@ public class SeqPanel extends JPanel int wrappedBlock = -1; /** + * Computes the column and sequence row (or possibly annotation row when in + * wrapped mode) for the given mouse position + * + * @param evt + * @return + */ + MousePos findMousePosition(MouseEvent evt) + { + int col = findColumn(evt); + int seq = -1; + int annIndex = -1; + int y = evt.getY(); + + int charHeight = av.getCharHeight(); + int alignmentHeight = av.getAlignment().getHeight(); + if (av.getWrapAlignment()) + { + int hgap = charHeight; + if (av.getScaleAboveWrapped()) + { + hgap += charHeight; + } + + final int alignmentHeightPixels = alignmentHeight * charHeight + hgap; + final int annotationHeight = seqCanvas.getAnnotationHeight(); + final int cHeight = alignmentHeightPixels + annotationHeight; + + int yOffsetPx = y % cHeight; // yPos below repeating width(s) + if (yOffsetPx > alignmentHeightPixels) + { + /* + * mouse is over annotations + */ + AlignmentAnnotation[] anns = av.getAlignment() + .getAlignmentAnnotation(); + int rowOffsetPx = yOffsetPx - alignmentHeightPixels; + annIndex = AnnotationPanel.getRowIndex(rowOffsetPx, anns); + } + else + { + /* + * mouse is over sequence (or the space above sequences) + */ + yOffsetPx -= hgap; + if (yOffsetPx > 0) + { + seq = Math.min(yOffsetPx / charHeight, alignmentHeight - 1); + } + } + } + else + { + seq = Math.min((y / charHeight) + av.getRanges().getStartSeq(), + alignmentHeight - 1); + } + int seqIndex = seq; + + return new MousePos(col, seqIndex, annIndex); + } + /** * Returns the aligned sequence position (base 0) at the mouse position, or * the closest visible one * @@ -253,32 +377,49 @@ public class SeqPanel extends JPanel } + /** + * Answers the index in the alignment (0...) of the sequence under the mouse + * position. If the mouse is below the alignment (say, over annotations), + * answers the index of the last sequence. + * + * @param evt + * @return + */ int findSeq(MouseEvent evt) { int seq = 0; int y = evt.getY(); + int charHeight = av.getCharHeight(); + int alignmentHeight = av.getAlignment().getHeight(); if (av.getWrapAlignment()) { - int hgap = av.getCharHeight(); + int hgap = charHeight; if (av.getScaleAboveWrapped()) { - hgap += av.getCharHeight(); + hgap += charHeight; } - int cHeight = av.getAlignment().getHeight() * av.getCharHeight() - + hgap + seqCanvas.getAnnotationHeight(); + int alignmentHeightPixels = alignmentHeight * charHeight; + int cHeight = alignmentHeightPixels + hgap + + seqCanvas.getAnnotationHeight(); y -= hgap; - seq = Math.min((y % cHeight) / av.getCharHeight(), - av.getAlignment().getHeight() - 1); + int yOffsetPx = y % cHeight; // yPos below repeating width(s) +// if (yOffsetPx > alignmentHeightPixels) +// { +// seq = -1; // cursor is over annotation or below alignment entirely +// } +// else + // { + seq = Math.min(yOffsetPx / charHeight, alignmentHeight - 1); +// } } else { - seq = Math.min( - (y / av.getCharHeight()) + av.getRanges().getStartSeq(), - av.getAlignment().getHeight() - 1); + seq = Math.min((y / charHeight) + av.getRanges().getStartSeq(), + alignmentHeight - 1); } return seq; @@ -777,23 +918,31 @@ public class SeqPanel extends JPanel mouseDragged(evt); } - final int column = findColumn(evt); - final 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 (column < 0 || seq < 0 || seq >= av.getAlignment().getHeight()) + if (mousePos.annotationIndex != -1) { - lastMouseSeq = -1; + mouseMovedOverAnnotation(mousePos); return; } - if (column == lastMouseColumn && seq == lastMouseSeq) + final int seq = mousePos.seqIndex;// findSeq(evt); + + final int column = mousePos.column; + if (column < 0 || seq < 0 || seq >= av.getAlignment().getHeight()) { - /* - * just a pixel move without change of residue - */ + lastMousePosition = null; + setToolTipText(null); + ap.alignFrame.statusBar.setText(""); return; } - lastMouseColumn = column; - lastMouseSeq = seq; SequenceI sequence = av.getAlignment().getSequenceAt(seq); @@ -871,6 +1020,34 @@ public class SeqPanel extends JPanel } } + /** + * 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 pos + * @param column + */ + protected void mouseMovedOverAnnotation(MousePos pos) + { + final int column = pos.column; + final int rowIndex = pos.annotationIndex; + + if (!av.getWrapAlignment() || !av.isShowAnnotation() || rowIndex < 0) + { + 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.statusBar.setText(msg); + } + private Point lastp = null; /* -- 1.7.10.2