X-Git-Url: http://source.jalview.org/gitweb/?a=blobdiff_plain;f=src%2Fjalview%2Fgui%2FSeqPanel.java;h=55f06fce3e56ad2b7c606c8dd797eaad68262c7b;hb=a9b20fb4f95f8a4435b09d513959443c266c719c;hp=a53edb9203a4f55080fb11989713db3c24314fc2;hpb=f484ffbf14ae725e55c80396be1889708988fd8b;p=jalview.git diff --git a/src/jalview/gui/SeqPanel.java b/src/jalview/gui/SeqPanel.java index a53edb9..55f06fc 100644 --- a/src/jalview/gui/SeqPanel.java +++ b/src/jalview/gui/SeqPanel.java @@ -20,8 +20,31 @@ */ 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; @@ -29,6 +52,7 @@ 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.SearchResultsI; @@ -49,23 +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.Collections; -import java.util.List; - -import javax.swing.JPanel; -import javax.swing.SwingUtilities; -import javax.swing.ToolTipManager; +import jalview.viewmodel.ViewportRanges; +import jalview.viewmodel.seqfeatures.FeatureRendererModel; /** * DOCUMENT ME! @@ -164,9 +173,9 @@ public class SeqPanel extends JPanel */ private MousePos lastMousePosition; - protected int lastres; + protected int editLastRes; - protected int startseq; + protected int editStartSeq; protected AlignViewport av; @@ -203,13 +212,21 @@ public class SeqPanel extends JPanel StringBuffer keyboardNo2; - java.net.URL linkImageURL; - private final SequenceAnnotationReport seqARep; - StringBuilder tooltipText = new StringBuilder(); + /* + * 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; @@ -225,11 +242,11 @@ public class SeqPanel extends JPanel */ 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 = viewport; setBackground(Color.white); @@ -257,6 +274,9 @@ public class SeqPanel extends JPanel /** * 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 @@ -313,15 +333,21 @@ public class SeqPanel extends JPanel } else { - seqIndex = Math.min((y / charHeight) + av.getRanges().getStartSeq(), + 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 @@ -411,8 +437,8 @@ public class SeqPanel extends JPanel /* * Tidy up come what may... */ - startseq = -1; - lastres = -1; + editStartSeq = -1; + editLastRes = -1; editingSeqs = false; groupEditing = false; keyboardNo1 = null; @@ -457,45 +483,85 @@ public class SeqPanel extends JPanel void moveCursor(int dx, int dy) { - seqCanvas.cursorX += dx; - seqCanvas.cursorY += dy; + moveCursor(dx, dy, false); + } + void moveCursor(int dx, int dy, boolean nextWord) + { HiddenColumns hidden = av.getAlignment().getHiddenColumns(); - if (av.hasHiddenColumns() && !hidden.isVisible(seqCanvas.cursorX)) + if (nextWord) { - int original = seqCanvas.cursorX - dx; int maxWidth = av.getAlignment().getWidth(); - - if (!hidden.isVisible(seqCanvas.cursorX)) - { - int visx = hidden.absoluteToVisibleColumn(seqCanvas.cursorX - dx); - int[] region = hidden.getRegionWithEdgeAtRes(visx); - - if (region != null) // just in case + 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) { - if (dx == 1) + r += dy; + if (r < 0) { - // moving right - seqCanvas.cursorX = region[1] + 1; + r = 0; } - else if (dx == -1) + if (r >= maxHeight) { - // moving left - seqCanvas.cursorX = region[0] - 1; + r = maxHeight - 1; } + seqAtRow = av.getAlignment().getSequenceAt(r); } - seqCanvas.cursorX = (seqCanvas.cursorX < 0) ? 0 : seqCanvas.cursorX; - } + 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 maxWidth = av.getAlignment().getWidth(); + seqCanvas.cursorX = nextVisible(hidden, maxWidth, seqCanvas.cursorX, + dx); + seqCanvas.cursorY += dy; + } + scrollToVisible(false); + } - if (seqCanvas.cursorX >= maxWidth - || !hidden.isVisible(seqCanvas.cursorX)) + 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 (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(false); + newCursorX = (newCursorX < 0) ? 0 : newCursorX; + if (newCursorX >= maxWidth || !hidden.isVisible(newCursorX)) + { + newCursorX = original; + } + return newCursorX; } /** @@ -553,7 +619,7 @@ public class SeqPanel extends JPanel if (av.getAlignment().getHiddenColumns().isVisible(seqCanvas.cursorX)) { setStatusMessage(av.getAlignment().getSequenceAt(seqCanvas.cursorY), - seqCanvas.cursorX, seqCanvas.cursorY); + seqCanvas.cursorX, seqCanvas.cursorY); } if (repaintNeeded) @@ -562,7 +628,6 @@ public class SeqPanel extends JPanel } } - void setSelectionAreaAtCursor(boolean topLeft) { SequenceI sequence = av.getAlignment().getSequenceAt(seqCanvas.cursorY); @@ -640,8 +705,8 @@ public class SeqPanel extends JPanel 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(); } @@ -649,8 +714,8 @@ public class SeqPanel extends JPanel 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(); } @@ -659,8 +724,8 @@ public class SeqPanel extends JPanel { // 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(); } @@ -742,13 +807,14 @@ public class SeqPanel extends JPanel return; } - if (!editingSeqs) + if (editingSeqs) + { + endEditing(); + } + else { doMouseReleasedDefineMode(evt, didDrag); - return; } - - endEditing(); } /** @@ -794,13 +860,13 @@ public class SeqPanel extends JPanel 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; @@ -828,11 +894,11 @@ public class SeqPanel extends JPanel * the start of the highlighted region. */ @Override - public void highlightSequence(SearchResultsI results) + public String highlightSequence(SearchResultsI results) { if (results == null || results.equals(lastSearchResults)) { - return; + return null; } lastSearchResults = results; @@ -845,7 +911,7 @@ public class SeqPanel extends JPanel // over residue to change abruptly, causing highlighted residue in panel 2 // to change, causing a scroll in panel 1 etc) ap.setToScrollComplementPanel(false); - wasScrolled = ap.scrollToPosition(results, false); + wasScrolled = ap.scrollToPosition(results); if (wasScrolled) { seqCanvas.revalidate(); @@ -853,11 +919,82 @@ public class SeqPanel extends JPanel ap.setToScrollComplementPanel(true); } - boolean noFastPaint = wasScrolled && av.getWrapAlignment(); - if (seqCanvas.highlightSearchResults(results, noFastPaint)) + boolean fastPaint = !(wasScrolled && av.getWrapAlignment()); + if (seqCanvas.highlightSearchResults(results, fastPaint)) { setStatusMessage(results); } + return results.isEmpty() ? null : getHighlightInfo(results); + } + + /** + * temporary hack: answers a message suitable to show on structure hover + * label. This is normally null. It is a peptide variation description if + *
+ * 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())
@@ -1352,30 +1570,38 @@ public class SeqPanel extends JPanel
}
}
- 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)
@@ -1387,12 +1613,17 @@ public class SeqPanel extends JPanel
message.append(" delete ");
}
- message.append(Math.abs(startres - lastres) + " gaps.");
+ 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;
@@ -1411,10 +1642,10 @@ public class SeqPanel extends JPanel
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;
@@ -1440,8 +1671,8 @@ public class SeqPanel extends JPanel
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;
@@ -1462,6 +1693,54 @@ public class SeqPanel extends JPanel
}
}
+ 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;
}
}
@@ -2335,7 +2806,7 @@ public class SeqPanel extends JPanel
{
if (av.getAlignment() == null)
{
- Cache.log.warn("alignviewport av SeqSetId=" + av.getSequenceSetId()
+ Console.warn("alignviewport av SeqSetId=" + av.getSequenceSetId()
+ " ViewId=" + av.getViewId()
+ " 's alignment is NULL! returning immediately.");
return;
@@ -2435,7 +2906,7 @@ public class SeqPanel extends JPanel
* 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);
/*
@@ -2447,7 +2918,7 @@ public class SeqPanel extends JPanel
HiddenColumns hs = new HiddenColumns();
MappingUtils.mapColumnSelection(colsel, hidden, sourceAv, av, cs, hs);
av.setColumnSelection(cs);
- av.getAlignment().setHiddenColumns(hs);
+ boolean hiddenChanged = av.getAlignment().setHiddenColumns(hs);
// lastly, update any dependent dialogs
if (ap.getCalculationDialog() != null)
@@ -2455,7 +2926,13 @@ public class SeqPanel extends JPanel
ap.getCalculationDialog().validateCalcTypes();
}
- PaintRefresher.Refresh(this, av.getSequenceSetId());
+ /*
+ * repaint alignment, and also Overview or Structure
+ * if hidden column selection has changed
+ */
+ ap.paintAlignment(hiddenChanged, hiddenChanged);
+ // propagate any selection changes
+ PaintRefresher.Refresh(ap, av.getSequenceSetId());
return true;
}