X-Git-Url: http://source.jalview.org/gitweb/?a=blobdiff_plain;f=src%2Fjalview%2Fgui%2FSeqPanel.java;h=20ec5e4f74f38f55a9600eb6dfdca6ac8fce12c9;hb=ee19482a42988b76fdca9d46065ce2ca14ea73c5;hp=648c3a22e0bbe65a8ad50984fb91cd6c05606ca5;hpb=3eef76298c55f3def21eed2516b5ace4405bed0a;p=jalview.git
diff --git a/src/jalview/gui/SeqPanel.java b/src/jalview/gui/SeqPanel.java
index 648c3a2..20ec5e4 100644
--- a/src/jalview/gui/SeqPanel.java
+++ b/src/jalview/gui/SeqPanel.java
@@ -1,56 +1,181 @@
/*
- * Jalview - A Sequence Alignment Editor and Viewer (Version 2.7)
- * Copyright (C) 2011 J Procter, AM Waterhouse, J Engelhardt, LM Lui, G Barton, M Clamp, S Searle
- *
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ *
* This file is part of Jalview.
- *
+ *
* Jalview is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
- *
- * Jalview is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *
+ * Jalview is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with Jalview. If not, see
+ * 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 findColumn(MouseEvent evt) { int res = 0; int x = evt.getX(); - if (av.wrapAlignment) - { + final int startRes = av.getRanges().getStartRes(); + final int charWidth = av.getCharWidth(); - int hgap = av.charHeight; - if (av.scaleAboveWrapped) + if (av.getWrapAlignment()) + { + int hgap = av.getCharHeight(); + if (av.getScaleAboveWrapped()) { - hgap += av.charHeight; + hgap += av.getCharHeight(); } - int cHeight = av.getAlignment().getHeight() * av.charHeight + hgap - + seqCanvas.getAnnotationHeight(); + int cHeight = av.getAlignment().getHeight() * av.getCharHeight() + + 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.getWidth()+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(); + /* + * 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.wrapAlignment) - { - int hgap = av.charHeight; - if (av.scaleAboveWrapped) - { - hgap += av.charHeight; - } - - int cHeight = av.getAlignment().getHeight() * av.charHeight + 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; } - SequenceFeature[] findFeaturesAtRes(SequenceI sequence, int res) + /** + * When all of a sequence of edits are complete, put the resulting edit list + * on the history stack (undo list), and reset flags for editing in progress. + */ + void endEditing() { - Vector tmp = new Vector(); - SequenceFeature[] features = sequence.getSequenceFeatures(); - if (features != null) + try { - for (int i = 0; i < features.length; i++) + if (editCommand != null && editCommand.getSize() > 0) { - if (av.featuresDisplayed == null - || !av.featuresDisplayed.containsKey(features[i].getType())) - { - continue; - } - - if (features[i].featureGroup != null - && seqCanvas.fr.featureGroups != null - && seqCanvas.fr.featureGroups - .containsKey(features[i].featureGroup) - && !((Boolean) seqCanvas.fr.featureGroups - .get(features[i].featureGroup)).booleanValue()) - continue; - - if ((features[i].getBegin() <= res) - && (features[i].getEnd() >= res)) - { - tmp.addElement(features[i]); - } + ap.alignFrame.addHistoryItem(editCommand); + av.firePropertyChange("alignment", null, + av.getAlignment().getSequences()); } - } - - features = new SequenceFeature[tmp.size()]; - tmp.copyInto(features); - - return features; - } - - void endEditing() - { - if (editCommand != null && editCommand.getSize() > 0) + } finally { - ap.alignFrame.addHistoryItem(editCommand); - av.firePropertyChange("alignment", null, av.getAlignment() - .getSequences()); + /* + * Tidy up come what may... + */ + editStartSeq = -1; + editLastRes = -1; + editingSeqs = false; + groupEditing = false; + keyboardNo1 = null; + keyboardNo2 = null; + editCommand = null; } - - startseq = -1; - lastres = -1; - editingSeqs = false; - groupEditing = false; - keyboardNo1 = null; - keyboardNo2 = null; - editCommand = null; } void setCursorRow() { seqCanvas.cursorY = getKeyboardNo1() - 1; - scrollToVisible(); + scrollToVisible(true); } void setCursorColumn() { seqCanvas.cursorX = getKeyboardNo1() - 1; - scrollToVisible(); + scrollToVisible(true); } void setCursorRowAndColumn() @@ -294,45 +469,108 @@ public class SeqPanel extends JPanel implements MouseListener, { seqCanvas.cursorX = getKeyboardNo1() - 1; seqCanvas.cursorY = getKeyboardNo2() - 1; - scrollToVisible(); + scrollToVisible(true); } } void setCursorPosition() { - SequenceI sequence = av.getAlignment().getSequenceAt( - seqCanvas.cursorY); + SequenceI sequence = av.getAlignment().getSequenceAt(seqCanvas.cursorY); - seqCanvas.cursorX = sequence.findIndex(getKeyboardNo1() - 1); - scrollToVisible(); + seqCanvas.cursorX = sequence.findIndex(getKeyboardNo1()) - 1; + 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) { @@ -353,50 +591,46 @@ public class SeqPanel extends JPanel implements MouseListener, } endEditing(); - if (av.wrapAlignment) + + 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.wrapAlignment) + 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) { - SequenceI sequence = av.getAlignment().getSequenceAt( - seqCanvas.cursorY); + SequenceI sequence = av.getAlignment().getSequenceAt(seqCanvas.cursorY); if (av.getSelectionGroup() != null) { @@ -464,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(); } @@ -480,18 +714,20 @@ 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(); } - void insertNucAtCursor(boolean group,String nuc){ - groupEditing = group; - startseq = seqCanvas.cursorY; - lastres = seqCanvas.cursorX; - editSequence(false, true, seqCanvas.cursorX + getKeyboardNo1()); - endEditing(); + void insertNucAtCursor(boolean group, String nuc) + { + // TODO not called - delete? + groupEditing = group; + editStartSeq = seqCanvas.cursorY; + editLastRes = seqCanvas.cursorX; + editSequence(false, true, seqCanvas.cursorX + getKeyboardNo1()); + endEditing(); } void numberPressed(char value) @@ -513,52 +749,77 @@ public class SeqPanel extends JPanel implements MouseListener, int getKeyboardNo1() { - if (keyboardNo1 == null) - return 1; - else + try + { + if (keyboardNo1 != null) + { + int value = Integer.parseInt(keyboardNo1.toString()); + keyboardNo1 = null; + return value; + } + } catch (Exception x) { - int value = Integer.parseInt(keyboardNo1.toString()); - keyboardNo1 = null; - return value; } + keyboardNo1 = null; + return 1; } int getKeyboardNo2() { - if (keyboardNo2 == null) - return 1; - else + try + { + if (keyboardNo2 != null) + { + int value = Integer.parseInt(keyboardNo2.toString()); + keyboardNo2 = null; + return value; + } + } catch (Exception x) { - int value = Integer.parseInt(keyboardNo2.toString()); - keyboardNo2 = null; - return value; } + keyboardNo2 = null; + return 1; } /** * DOCUMENT ME! - * + * * @param evt * DOCUMENT ME! */ @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 (!editingSeqs) + if (evt.isPopupTrigger()) // Windows: mouseReleased { - doMouseReleasedDefineMode(evt); + showPopupMenu(evt, pos); + evt.consume(); return; } - endEditing(); + if (editingSeqs) + { + endEditing(); + } + else + { + doMouseReleasedDefineMode(evt, didDrag); + } } /** * DOCUMENT ME! - * + * * @param evt * DOCUMENT ME! */ @@ -566,45 +827,46 @@ 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 (javax.swing.SwingUtilities.isMiddleMouseButton(evt)) + if (SwingUtilities.isMiddleMouseButton(evt)) { mouseWheelPressed = true; 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; @@ -625,75 +887,204 @@ public class SeqPanel extends JPanel implements MouseListener, lastMessage = tmp; } + /** + * Highlight the mapped region described by the search results object (unless + * unchanged). This supports highlight of protein while mousing over linked + * cDNA and vice versa. The status bar is also updated to show the location of + * the start of the highlighted region. + */ @Override - public void highlightSequence(SearchResults results) + public String highlightSequence(SearchResultsI results) { - if (av.followHighlight) + if (results == null || results.equals(lastSearchResults)) + { + return null; + } + lastSearchResults = results; + + boolean wasScrolled = false; + + if (av.isFollowHighlight()) { - 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(); + seqCanvas.revalidate(); } + ap.setToScrollComplementPanel(true); } - seqCanvas.highlightSearchResults(results); - } - @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! - * - * @param evt - * 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) */ - int setStatusMessage(SequenceI sequence, int res, int seq) + protected void setStatusMessage(String seqName, int seqIndex, + char sequenceChar, int residuePos) { - int pos = -1; - StringBuffer text = new StringBuffer("Sequence " + (seq + 1) + " ID: " - + sequence.getName()); + StringBuilder text = new StringBuilder(32); + + /* + * Sequence number (if known), and sequence name. + */ + 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). + */ + boolean isGapped = Comparison.isGap(sequenceChar); - Object obj = null; - if (av.getAlignment().isNucleotide()) + if (!isGapped) { - obj = ResidueProperties.nucleotideName.get(sequence.getCharAt(res) - + ""); - if (obj != null) + boolean nucleotide = av.getAlignment().isNucleotide(); + String displayChar = String.valueOf(sequenceChar); + if (nucleotide) { - text.append(" Nucleotide: "); + residue = ResidueProperties.nucleotideName.get(displayChar); } - } - else - { - obj = ResidueProperties.aa2Triplet.get(sequence.getCharAt(res) + ""); - if (obj != null) + else { - text.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); + + text.append(" (").append(Integer.toString(residuePos)).append(")"); } + ap.alignFrame.setStatus(text.toString()); + } - if (obj != null) + /** + * Set the status bar message to highlight the first matched position in + * search results. + * + * @param results + */ + private void setStatusMessage(SearchResultsI results) + { + AlignmentI al = this.av.getAlignment(); + int sequenceIndex = al.findIndex(results); + if (sequenceIndex == -1) + { + return; + } + SequenceI alignedSeq = al.getSequenceAt(sequenceIndex); + SequenceI ds = alignedSeq.getDatasetSequence(); + for (SearchResultMatchI m : results.getResults()) { - pos = sequence.findPosition(res); - if (obj != "") + SequenceI seq = m.getSequence(); + if (seq.getDatasetSequence() != null) + { + seq = seq.getDatasetSequence(); + } + + if (seq == ds) { - text.append(obj + " (" + pos + ")"); + int start = m.getStart(); + setStatusMessage(alignedSeq.getName(), sequenceIndex, + seq.getCharAt(start - 1), start); + return; } } - ap.alignFrame.statusBar.setText(text.toString()); - return pos; } /** - * 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) { - int oldWidth = av.charWidth; + 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++; } @@ -845,26 +1430,60 @@ public class SeqPanel extends JPanel implements MouseListener, fontSize = 1; } - av.setFont(new Font(av.font.getName(), av.font.getStyle(), fontSize)); - av.charWidth = 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 { - if (evt.getX() < lastMousePress.getX() && av.charWidth > 1) + /* + * on drag left or right, decrement or increment character width + */ + int newWidth = 0; + if (evt.getX() < lastMousePress.getX() && av.getCharWidth() > 1) { - av.charWidth--; + newWidth = av.getCharWidth() - 1; + av.setCharWidth(newWidth); } else if (evt.getX() > lastMousePress.getX()) { - av.charWidth++; + 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()); - av.validCharWidth = fm.charWidth('M') <= av.charWidth; + av.validCharWidth = fm.charWidth('M') <= av.getCharWidth(); lastMousePress = evt.getPoint(); @@ -873,48 +1492,73 @@ 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); + editSequence(false, false, res); } else { - editSequence(true, false,res); + editSequence(true, false, res); } mouseDragging = true; if (scrollThread != null) { - scrollThread.setEvent(evt); + scrollThread.setMousePosition(evt.getPoint()); } } - //TODO: Make it more clever than many booleans - synchronized void editSequence(boolean insertGap, boolean editSeq, int startres) + /** + * 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,
+ 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())
@@ -926,27 +1570,38 @@ public class SeqPanel extends JPanel implements MouseListener,
}
}
- StringBuffer message = new StringBuffer();
+ 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("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("Edit " + 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)
@@ -958,13 +1613,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;
@@ -983,10 +1642,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;
@@ -1007,11 +1666,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;
@@ -1032,6 +1693,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;
}
}
@@ -1776,67 +2747,98 @@ 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.
- if (av == source
- || !av.followSelection
- || (av.isSelectionGroupChanged(false) || av.isColSelChanged(false))
- || (source instanceof AlignViewport && ((AlignViewport) source)
- .getSequenceSetId().equals(av.getSequenceSetId())))
+ 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;
}
+
+ /*
+ * Ignore the selection if there is one of our own pending.
+ */
+ if (av.isSelectionGroupChanged(false) || av.isColSelChanged(false))
+ {
+ return;
+ }
+
+ /*
+ * Check for selection in a view of which this one is a dna/protein
+ * complement.
+ */
+ if (selectionFromTranslation(seqsel, colsel, hidden, source))
+ {
+ return;
+ }
+
// do we want to thread this ? (contention with seqsel and colsel locks, I
// suspect)
- // rules are: colsel is copied if there is a real intersection between
- // sequence selection
- boolean repaint = false, copycolsel = true;
- // if (!av.isSelectionGroupChanged(false))
+ /*
+ * only copy colsel if there is a real intersection between
+ * sequence selection and this panel's alignment
+ */
+ boolean repaint = false;
+ boolean copycolsel = false;
+
+ SequenceGroup sgroup = null;
+ if (seqsel != null && seqsel.getSize() > 0)
{
- SequenceGroup sgroup = null;
- if (seqsel != null && seqsel.getSize()>0)
- {
- if (av.getAlignment() == null)
- {
- jalview.bin.Cache.log.warn("alignviewport av SeqSetId="
- + av.getSequenceSetId() + " ViewId=" + av.getViewId()
- + " 's alignment is NULL! returning immediatly.");
- return;
- }
- sgroup = seqsel.intersect(av.getAlignment(),
- (av.hasHiddenRows()) ? av.getHiddenRepSequences() : null);
- if ((sgroup == null || sgroup.getSize() == 0)
- || (colsel == null || colsel.size() == 0))
- {
- // don't copy columns if the region didn't intersect.
- copycolsel = false;
- }
- }
- if (sgroup != null && sgroup.getSize() > 0)
+ if (av.getAlignment() == null)
{
- av.setSelectionGroup(sgroup);
+ Console.warn("alignviewport av SeqSetId=" + av.getSequenceSetId()
+ + " ViewId=" + av.getViewId()
+ + " 's alignment is NULL! returning immediately.");
+ return;
}
- else
+ sgroup = seqsel.intersect(av.getAlignment(),
+ (av.hasHiddenRows()) ? av.getHiddenRepSequences() : null);
+ if ((sgroup != null && sgroup.getSize() > 0))
{
- av.setSelectionGroup(null);
+ copycolsel = true;
}
- av.isSelectionGroupChanged(true);
- repaint = true;
}
+ if (sgroup != null && sgroup.getSize() > 0)
+ {
+ av.setSelectionGroup(sgroup);
+ }
+ else
+ {
+ av.setSelectionGroup(null);
+ }
+ av.isSelectionGroupChanged(true);
+ repaint = true;
+
if (copycolsel)
{
// the current selection is unset or from a previous message
// so import the new colsel.
- if (colsel == null || colsel.size() == 0)
+ if (colsel == null || colsel.isEmpty())
{
if (av.getColumnSelection() != null)
{
av.getColumnSelection().clear();
- repaint=true;
+ repaint = true;
}
}
else
@@ -1848,22 +2850,97 @@ 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))
+ && (av.getAlignment().getHiddenColumns() == null))
{
System.err.println("Bad things");
}
- if (repaint)
+ if (repaint) // always true!
{
// probably finessing with multiple redraws here
PaintRefresher.Refresh(this, av.getSequenceSetId());
// ap.paintAlignment(false);
}
+
+ // lastly, update dependent dialogs
+ if (ap.getCalculationDialog() != null)
+ {
+ ap.getCalculationDialog().validateCalcTypes();
+ }
+
+ }
+
+ /**
+ * If this panel is a cdna/protein translation view of the selection source,
+ * tries to map the source selection to a local one, and returns true. Else
+ * returns false.
+ *
+ * @param seqsel
+ * @param colsel
+ * @param source
+ */
+ protected boolean selectionFromTranslation(SequenceGroup seqsel,
+ ColumnSelection colsel, HiddenColumns hidden,
+ SelectionSource source)
+ {
+ if (!(source instanceof AlignViewportI))
+ {
+ return false;
+ }
+ final AlignViewportI sourceAv = (AlignViewportI) source;
+ if (sourceAv.getCodingComplement() != av
+ && av.getCodingComplement() != sourceAv)
+ {
+ return false;
+ }
+
+ /*
+ * Map sequence selection
+ */
+ SequenceGroup sg = MappingUtils.mapSequenceGroup(seqsel, sourceAv, av);
+ av.setSelectionGroup(sg != null && sg.getSize() > 0 ? sg : null);
+ av.isSelectionGroupChanged(true);
+
+ /*
+ * Map column selection
+ */
+ // 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);
+
+ // 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;
}
}