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 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;
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 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;
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);
}
-
- lastMouseColumn = -1;
- lastMouseSeq = -1;
}
int startWrapBlock = -1;
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);
+ // also last sequence in alignment (for backwards compatible behaviour)
+ seq = alignmentHeight - 1;
+ }
+ 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
*
}
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();
- }
+ /*
+ * make sure we calculate relative to visible alignment,
+ * rather than right-hand gutter
+ */
+ x = Math.min(x, seqCanvas.getX() + seqCanvas.getWidth());
res = (x / av.getCharWidth()) + startRes;
- if (res > av.getRanges().getEndRes())
- {
- // moused off right
- res = av.getRanges().getEndRes();
- }
+ res = Math.min(res, av.getRanges().getEndRes());
}
if (av.hasHiddenColumns())
}
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;
}
/**
@Override
public void mouseReleased(MouseEvent evt)
{
+ MousePos pos = findMousePosition(evt);
+ if (pos.annotationIndex != -1)
+ {
+ // mouse is over annotation row in wrapped mode
+ 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;
}
public void mousePressed(MouseEvent evt)
{
lastMousePress = evt.getPoint();
+ MousePos pos = findMousePosition(evt);
+ if (pos.annotationIndex != -1)
+ {
+ // mouse is over an annotation row in wrapped mode
+ return;
+ }
if (SwingUtilities.isMiddleMouseButton(evt))
{
}
else
{
- doMousePressedDefineMode(evt);
+ doMousePressedDefineMode(evt, pos);
return;
}
- int seq = findSeq(evt);
- int res = findColumn(evt);
+ int seq = pos.seqIndex;
+ int res = pos.column;
if (seq < 0 || res < 0)
{
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.isOverAnnotation())
{
- lastMouseSeq = -1;
+ mouseMovedOverAnnotation(mousePos);
return;
}
- if (column == lastMouseColumn && seq == lastMouseSeq)
+ final int seq = mousePos.seqIndex;
+
+ 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);
List<SequenceFeature> features = ap.getFeatureRenderer()
.findFeaturesAtColumn(sequence, column + 1);
seqARep.appendFeatures(tooltipText, pos, features,
- this.ap.getSeqPanel().seqCanvas.fr.getMinMax());
+ this.ap.getSeqPanel().seqCanvas.fr);
}
if (tooltipText.length() == 6) // <html>
{
}
else
{
+ if (tooltipText.length() > MAX_TOOLTIP_LENGTH) // constant
+ {
+ tooltipText.setLength(MAX_TOOLTIP_LENGTH);
+ tooltipText.append("...");
+ }
String textString = tooltipText.toString();
if (lastTooltip == null || !lastTooltip.equals(textString))
{
}
}
+ /**
+ * 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;
/*
@Override
public void mouseDragged(MouseEvent evt)
{
+ MousePos pos = findMousePosition(evt);
+ if (pos.isOverAnnotation())
+ {
+ // mouse is over annotation row in wrapped mode
+ return;
+ }
+
if (mouseWheelPressed)
{
boolean inSplitFrame = ap.av.getCodingComplement() != null;
return;
}
- int res = findColumn(evt);
+ int res = pos.column;
if (res < 0)
{
public void mouseClicked(MouseEvent evt)
{
SequenceGroup sg = null;
- SequenceI sequence = av.getAlignment().getSequenceAt(findSeq(evt));
+ MousePos pos = findMousePosition(evt);
+ if (pos.isOverAnnotation())
+ {
+ // mouse is over annotation label in wrapped mode
+ return;
+ }
+
if (evt.getClickCount() > 1)
{
sg = av.getSelectionGroup();
av.setSelectionGroup(null);
}
- int column = findColumn(evt);
+ 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);// findSeq(evt));
List<SequenceFeature> features = seqCanvas.getFeatureRenderer()
.findFeaturesAtColumn(sequence, column + 1);
/**
* 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);
+ if (pos.isOverAnnotation())
+ {
+ // JvOptionPane.showInternalMessageDialog(Desktop.desktop,
+ // MessageManager.getString(
+ // "label.cannot_edit_annotations_in_wrapped_view"),
+ // MessageManager.getString("label.wrapped_view_no_edit"),
+ // JvOptionPane.WARNING_MESSAGE);
+ return;
+ }
+
+ final int res = pos.column;
+ final int seq = pos.seqIndex;
oldSeq = seq;
updateOverviewAndStructs = false;
startWrapBlock = wrappedBlock;
- if (av.getWrapAlignment() && seq > av.getAlignment().getHeight())
- {
- 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;
if (evt.isPopupTrigger()) // Mac: mousePressed
{
- showPopupMenu(evt);
+ showPopupMenu(evt, pos);
return;
}
if (av.cursorMode)
{
- seqCanvas.cursorX = findColumn(evt);
- seqCanvas.cursorY = findSeq(evt);
+ seqCanvas.cursorX = res;
+ seqCanvas.cursorY = seq;
seqCanvas.repaint();
return;
}
/**
* 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 column = findColumn(evt);
- final int seq = findSeq(evt);
+ final int column = pos.column;
+ final int seq = pos.seqIndex;
SequenceI sequence = av.getAlignment().getSequenceAt(seq);
- List<SequenceFeature> allFeatures = ap.getFeatureRenderer()
+ List<SequenceFeature> features = ap.getFeatureRenderer()
.findFeaturesAtColumn(sequence, column + 1);
- List<String> links = new ArrayList<>();
- for (SequenceFeature sf : allFeatures)
- {
- if (sf.links != null)
- {
- for (String link : sf.links)
- {
- links.add(link);
- }
- }
- }
- PopupMenu pop = new PopupMenu(ap, null, links);
+ PopupMenu pop = new PopupMenu(ap, null, features);
pop.show(this, evt.getX(), evt.getY());
}
*/
public void doMouseDraggedDefineMode(MouseEvent evt)
{
- int res = findColumn(evt);
- int y = findSeq(evt);
+ MousePos pos = findMousePosition(evt);
+ if (pos.isOverAnnotation())
+ {
+ // mouse is over annotation in wrapped mode
+ return;
+ }
+
+ int res = pos.column;
+ int y = pos.seqIndex;
if (wrappedBlock != startWrapBlock)
{
return true;
}
+
+ /**
+ *
+ * @return null or last search results handled by this panel
+ */
+ public SearchResultsI getLastSearchResults()
+ {
+ return lastSearchResults;
+ }
}