2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
7 * Jalview is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation, either version 3
10 * of the License, or (at your option) any later version.
12 * Jalview is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15 * PURPOSE. See the GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19 * The Jalview Authors are detailed in the 'AUTHORS' file.
23 import jalview.api.AlignViewportI;
24 import jalview.bin.Cache;
25 import jalview.commands.EditCommand;
26 import jalview.commands.EditCommand.Action;
27 import jalview.commands.EditCommand.Edit;
28 import jalview.datamodel.AlignmentAnnotation;
29 import jalview.datamodel.AlignmentI;
30 import jalview.datamodel.ColumnSelection;
31 import jalview.datamodel.HiddenColumns;
32 import jalview.datamodel.SearchResultMatchI;
33 import jalview.datamodel.SearchResults;
34 import jalview.datamodel.SearchResultsI;
35 import jalview.datamodel.Sequence;
36 import jalview.datamodel.SequenceFeature;
37 import jalview.datamodel.SequenceGroup;
38 import jalview.datamodel.SequenceI;
39 import jalview.io.SequenceAnnotationReport;
40 import jalview.renderer.ResidueShaderI;
41 import jalview.schemes.ResidueProperties;
42 import jalview.structure.SelectionListener;
43 import jalview.structure.SelectionSource;
44 import jalview.structure.SequenceListener;
45 import jalview.structure.StructureSelectionManager;
46 import jalview.structure.VamsasSource;
47 import jalview.util.Comparison;
48 import jalview.util.MappingUtils;
49 import jalview.util.MessageManager;
50 import jalview.util.Platform;
51 import jalview.viewmodel.AlignmentViewport;
52 import jalview.viewmodel.ViewportRanges;
54 import java.awt.BorderLayout;
55 import java.awt.Color;
57 import java.awt.FontMetrics;
58 import java.awt.Point;
59 import java.awt.event.MouseEvent;
60 import java.awt.event.MouseListener;
61 import java.awt.event.MouseMotionListener;
62 import java.awt.event.MouseWheelEvent;
63 import java.awt.event.MouseWheelListener;
64 import java.util.Collections;
65 import java.util.List;
67 import javax.swing.JPanel;
68 import javax.swing.SwingUtilities;
69 import javax.swing.ToolTipManager;
75 * @version $Revision: 1.130 $
77 public class SeqPanel extends JPanel
78 implements MouseListener, MouseMotionListener, MouseWheelListener,
79 SequenceListener, SelectionListener
82 * a class that holds computed mouse position
83 * - column of the alignment (0...)
84 * - sequence offset (0...)
85 * - annotation row offset (0...)
86 * where annotation offset is -1 unless the alignment is shown
87 * in wrapped mode, annotations are shown, and the mouse is
88 * over an annnotation row
93 * alignment column position of cursor (0...)
98 * index in alignment of sequence under cursor,
99 * or nearest above if cursor is not over a sequence
104 * index in annotations array of annotation under the cursor
105 * (only possible in wrapped mode with annotations shown),
106 * or -1 if cursor is not over an annotation row
108 final int annotationIndex;
110 MousePos(int col, int seq, int ann)
114 annotationIndex = ann;
117 boolean isOverAnnotation()
119 return annotationIndex != -1;
123 public boolean equals(Object obj)
125 if (obj == null || !(obj instanceof MousePos))
129 MousePos o = (MousePos) obj;
130 boolean b = (column == o.column && seqIndex == o.seqIndex
131 && annotationIndex == o.annotationIndex);
132 // System.out.println(obj + (b ? "= " : "!= ") + this);
137 * A simple hashCode that ensures that instances that satisfy equals() have
141 public int hashCode()
143 return column + seqIndex + annotationIndex;
147 * toString method for debug output purposes only
150 public String toString()
152 return String.format("c%d:s%d:a%d", column, seqIndex,
157 private static final int MAX_TOOLTIP_LENGTH = 300;
159 public SeqCanvas seqCanvas;
161 public AlignmentPanel ap;
164 * last position for mouseMoved event
166 private MousePos lastMousePosition;
168 protected int editLastRes;
170 protected int editStartSeq;
172 protected AlignViewport av;
174 ScrollThread scrollThread = null;
176 boolean mouseDragging = false;
178 boolean editingSeqs = false;
180 boolean groupEditing = false;
182 // ////////////////////////////////////////
183 // ///Everything below this is for defining the boundary of the rubberband
184 // ////////////////////////////////////////
187 boolean changeEndSeq = false;
189 boolean changeStartSeq = false;
191 boolean changeEndRes = false;
193 boolean changeStartRes = false;
195 SequenceGroup stretchGroup = null;
197 boolean remove = false;
199 Point lastMousePress;
201 boolean mouseWheelPressed = false;
203 StringBuffer keyboardNo1;
205 StringBuffer keyboardNo2;
207 java.net.URL linkImageURL;
209 private final SequenceAnnotationReport seqARep;
211 StringBuilder tooltipText = new StringBuilder();
215 EditCommand editCommand;
217 StructureSelectionManager ssm;
219 SearchResultsI lastSearchResults;
222 * Creates a new SeqPanel object
227 public SeqPanel(AlignViewport viewport, AlignmentPanel alignPanel)
229 linkImageURL = getClass().getResource("/images/link.gif");
230 seqARep = new SequenceAnnotationReport(linkImageURL.toString());
231 ToolTipManager.sharedInstance().registerComponent(this);
232 ToolTipManager.sharedInstance().setInitialDelay(0);
233 ToolTipManager.sharedInstance().setDismissDelay(10000);
235 setBackground(Color.white);
237 seqCanvas = new SeqCanvas(alignPanel);
238 setLayout(new BorderLayout());
239 add(seqCanvas, BorderLayout.CENTER);
241 this.ap = alignPanel;
243 if (!viewport.isDataset())
245 addMouseMotionListener(this);
246 addMouseListener(this);
247 addMouseWheelListener(this);
248 ssm = viewport.getStructureSelectionManager();
249 ssm.addStructureViewerListener(this);
250 ssm.addSelectionListener(this);
254 int startWrapBlock = -1;
256 int wrappedBlock = -1;
259 * Computes the column and sequence row (and possibly annotation row when in
260 * wrapped mode) for the given mouse position
265 MousePos findMousePosition(MouseEvent evt)
267 int col = findColumn(evt);
272 int charHeight = av.getCharHeight();
273 int alignmentHeight = av.getAlignment().getHeight();
274 if (av.getWrapAlignment())
276 seqCanvas.calculateWrappedGeometry(seqCanvas.getWidth(),
277 seqCanvas.getHeight());
280 * yPos modulo height of repeating width
282 int yOffsetPx = y % seqCanvas.wrappedRepeatHeightPx;
285 * height of sequences plus space / scale above,
286 * plus gap between sequences and annotations
288 int alignmentHeightPixels = seqCanvas.wrappedSpaceAboveAlignment
289 + alignmentHeight * charHeight
290 + SeqCanvas.SEQS_ANNOTATION_GAP;
291 if (yOffsetPx >= alignmentHeightPixels)
294 * mouse is over annotations; find annotation index, also set
295 * last sequence above (for backwards compatible behaviour)
297 AlignmentAnnotation[] anns = av.getAlignment()
298 .getAlignmentAnnotation();
299 int rowOffsetPx = yOffsetPx - alignmentHeightPixels;
300 annIndex = AnnotationPanel.getRowIndex(rowOffsetPx, anns);
301 seqIndex = alignmentHeight - 1;
306 * mouse is over sequence (or the space above sequences)
308 yOffsetPx -= seqCanvas.wrappedSpaceAboveAlignment;
311 seqIndex = Math.min(yOffsetPx / charHeight, alignmentHeight - 1);
317 ViewportRanges ranges = av.getRanges();
318 seqIndex = Math.min((y / charHeight) + ranges.getStartSeq(),
319 alignmentHeight - 1);
320 seqIndex = Math.min(seqIndex, ranges.getEndSeq());
323 return new MousePos(col, seqIndex, annIndex);
326 * Returns the aligned sequence position (base 0) at the mouse position, or
327 * the closest visible one
332 int findColumn(MouseEvent evt)
337 final int startRes = av.getRanges().getStartRes();
338 final int charWidth = av.getCharWidth();
340 if (av.getWrapAlignment())
342 int hgap = av.getCharHeight();
343 if (av.getScaleAboveWrapped())
345 hgap += av.getCharHeight();
348 int cHeight = av.getAlignment().getHeight() * av.getCharHeight()
349 + hgap + seqCanvas.getAnnotationHeight();
352 y = Math.max(0, y - hgap);
353 x -= seqCanvas.getLabelWidthWest();
356 // mouse is over left scale
360 int cwidth = seqCanvas.getWrappedCanvasWidth(this.getWidth());
365 if (x >= cwidth * charWidth)
367 // mouse is over right scale
371 wrappedBlock = y / cHeight;
372 wrappedBlock += startRes / cwidth;
373 // allow for wrapped view scrolled right (possible from Overview)
374 int startOffset = startRes % cwidth;
375 res = wrappedBlock * cwidth + startOffset
376 + Math.min(cwidth - 1, x / charWidth);
381 * make sure we calculate relative to visible alignment,
382 * rather than right-hand gutter
384 x = Math.min(x, seqCanvas.getX() + seqCanvas.getWidth());
385 res = (x / charWidth) + startRes;
386 res = Math.min(res, av.getRanges().getEndRes());
389 if (av.hasHiddenColumns())
391 res = av.getAlignment().getHiddenColumns()
392 .visibleToAbsoluteColumn(res);
399 * When all of a sequence of edits are complete, put the resulting edit list
400 * on the history stack (undo list), and reset flags for editing in progress.
406 if (editCommand != null && editCommand.getSize() > 0)
408 ap.alignFrame.addHistoryItem(editCommand);
409 av.firePropertyChange("alignment", null,
410 av.getAlignment().getSequences());
415 * Tidy up come what may...
420 groupEditing = false;
429 seqCanvas.cursorY = getKeyboardNo1() - 1;
430 scrollToVisible(true);
433 void setCursorColumn()
435 seqCanvas.cursorX = getKeyboardNo1() - 1;
436 scrollToVisible(true);
439 void setCursorRowAndColumn()
441 if (keyboardNo2 == null)
443 keyboardNo2 = new StringBuffer();
447 seqCanvas.cursorX = getKeyboardNo1() - 1;
448 seqCanvas.cursorY = getKeyboardNo2() - 1;
449 scrollToVisible(true);
453 void setCursorPosition()
455 SequenceI sequence = av.getAlignment().getSequenceAt(seqCanvas.cursorY);
457 seqCanvas.cursorX = sequence.findIndex(getKeyboardNo1()) - 1;
458 scrollToVisible(true);
461 void moveCursor(int dx, int dy)
463 seqCanvas.cursorX += dx;
464 seqCanvas.cursorY += dy;
466 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
468 if (av.hasHiddenColumns() && !hidden.isVisible(seqCanvas.cursorX))
470 int original = seqCanvas.cursorX - dx;
471 int maxWidth = av.getAlignment().getWidth();
473 if (!hidden.isVisible(seqCanvas.cursorX))
475 int visx = hidden.absoluteToVisibleColumn(seqCanvas.cursorX - dx);
476 int[] region = hidden.getRegionWithEdgeAtRes(visx);
478 if (region != null) // just in case
483 seqCanvas.cursorX = region[1] + 1;
488 seqCanvas.cursorX = region[0] - 1;
491 seqCanvas.cursorX = (seqCanvas.cursorX < 0) ? 0 : seqCanvas.cursorX;
494 if (seqCanvas.cursorX >= maxWidth
495 || !hidden.isVisible(seqCanvas.cursorX))
497 seqCanvas.cursorX = original;
501 scrollToVisible(false);
505 * Scroll to make the cursor visible in the viewport.
508 * just jump to the location rather than scrolling
510 void scrollToVisible(boolean jump)
512 if (seqCanvas.cursorX < 0)
514 seqCanvas.cursorX = 0;
516 else if (seqCanvas.cursorX > av.getAlignment().getWidth() - 1)
518 seqCanvas.cursorX = av.getAlignment().getWidth() - 1;
521 if (seqCanvas.cursorY < 0)
523 seqCanvas.cursorY = 0;
525 else if (seqCanvas.cursorY > av.getAlignment().getHeight() - 1)
527 seqCanvas.cursorY = av.getAlignment().getHeight() - 1;
532 boolean repaintNeeded = true;
535 // only need to repaint if the viewport did not move, as otherwise it will
537 repaintNeeded = !av.getRanges().setViewportLocation(seqCanvas.cursorX,
542 if (av.getWrapAlignment())
544 // scrollToWrappedVisible expects x-value to have hidden cols subtracted
545 int x = av.getAlignment().getHiddenColumns()
546 .absoluteToVisibleColumn(seqCanvas.cursorX);
547 av.getRanges().scrollToWrappedVisible(x);
551 av.getRanges().scrollToVisible(seqCanvas.cursorX,
556 if (av.getAlignment().getHiddenColumns().isVisible(seqCanvas.cursorX))
558 setStatusMessage(av.getAlignment().getSequenceAt(seqCanvas.cursorY),
559 seqCanvas.cursorX, seqCanvas.cursorY);
569 void setSelectionAreaAtCursor(boolean topLeft)
571 SequenceI sequence = av.getAlignment().getSequenceAt(seqCanvas.cursorY);
573 if (av.getSelectionGroup() != null)
575 SequenceGroup sg = av.getSelectionGroup();
576 // Find the top and bottom of this group
577 int min = av.getAlignment().getHeight(), max = 0;
578 for (int i = 0; i < sg.getSize(); i++)
580 int index = av.getAlignment().findIndex(sg.getSequenceAt(i));
595 sg.setStartRes(seqCanvas.cursorX);
596 if (sg.getEndRes() < seqCanvas.cursorX)
598 sg.setEndRes(seqCanvas.cursorX);
601 min = seqCanvas.cursorY;
605 sg.setEndRes(seqCanvas.cursorX);
606 if (sg.getStartRes() > seqCanvas.cursorX)
608 sg.setStartRes(seqCanvas.cursorX);
611 max = seqCanvas.cursorY + 1;
616 // Only the user can do this
617 av.setSelectionGroup(null);
621 // Now add any sequences between min and max
622 sg.getSequences(null).clear();
623 for (int i = min; i < max; i++)
625 sg.addSequence(av.getAlignment().getSequenceAt(i), false);
630 if (av.getSelectionGroup() == null)
632 SequenceGroup sg = new SequenceGroup();
633 sg.setStartRes(seqCanvas.cursorX);
634 sg.setEndRes(seqCanvas.cursorX);
635 sg.addSequence(sequence, false);
636 av.setSelectionGroup(sg);
639 ap.paintAlignment(false, false);
643 void insertGapAtCursor(boolean group)
645 groupEditing = group;
646 editStartSeq = seqCanvas.cursorY;
647 editLastRes = seqCanvas.cursorX;
648 editSequence(true, false, seqCanvas.cursorX + getKeyboardNo1());
652 void deleteGapAtCursor(boolean group)
654 groupEditing = group;
655 editStartSeq = seqCanvas.cursorY;
656 editLastRes = seqCanvas.cursorX + getKeyboardNo1();
657 editSequence(false, false, seqCanvas.cursorX);
661 void insertNucAtCursor(boolean group, String nuc)
663 // TODO not called - delete?
664 groupEditing = group;
665 editStartSeq = seqCanvas.cursorY;
666 editLastRes = seqCanvas.cursorX;
667 editSequence(false, true, seqCanvas.cursorX + getKeyboardNo1());
671 void numberPressed(char value)
673 if (keyboardNo1 == null)
675 keyboardNo1 = new StringBuffer();
678 if (keyboardNo2 != null)
680 keyboardNo2.append(value);
684 keyboardNo1.append(value);
692 if (keyboardNo1 != null)
694 int value = Integer.parseInt(keyboardNo1.toString());
698 } catch (Exception x)
709 if (keyboardNo2 != null)
711 int value = Integer.parseInt(keyboardNo2.toString());
715 } catch (Exception x)
729 public void mouseReleased(MouseEvent evt)
731 MousePos pos = findMousePosition(evt);
732 if (pos.isOverAnnotation() || pos.seqIndex == -1 || pos.column == -1)
737 boolean didDrag = mouseDragging; // did we come here after a drag
738 mouseDragging = false;
739 mouseWheelPressed = false;
741 if (evt.isPopupTrigger()) // Windows: mouseReleased
743 showPopupMenu(evt, pos);
754 doMouseReleasedDefineMode(evt, didDrag);
765 public void mousePressed(MouseEvent evt)
767 lastMousePress = evt.getPoint();
768 MousePos pos = findMousePosition(evt);
769 if (pos.isOverAnnotation() || pos.seqIndex == -1 || pos.column == -1)
774 if (SwingUtilities.isMiddleMouseButton(evt))
776 mouseWheelPressed = true;
780 boolean isControlDown = Platform.isControlDown(evt);
781 if (evt.isShiftDown() || isControlDown)
791 doMousePressedDefineMode(evt, pos);
795 int seq = pos.seqIndex;
796 int res = pos.column;
798 if ((seq < av.getAlignment().getHeight())
799 && (res < av.getAlignment().getSequenceAt(seq).getLength()))
816 public void mouseOverSequence(SequenceI sequence, int index, int pos)
818 String tmp = sequence.hashCode() + " " + index + " " + pos;
820 if (lastMessage == null || !lastMessage.equals(tmp))
822 // System.err.println("mouseOver Sequence: "+tmp);
823 ssm.mouseOverSequence(sequence, index, pos, av);
829 * Highlight the mapped region described by the search results object (unless
830 * unchanged). This supports highlight of protein while mousing over linked
831 * cDNA and vice versa. The status bar is also updated to show the location of
832 * the start of the highlighted region.
835 public void highlightSequence(SearchResultsI results)
837 if (results == null || results.equals(lastSearchResults))
841 lastSearchResults = results;
843 boolean wasScrolled = false;
845 if (av.isFollowHighlight())
847 // don't allow highlight of protein/cDNA to also scroll a complementary
848 // panel,as this sets up a feedback loop (scrolling panel 1 causes moused
849 // over residue to change abruptly, causing highlighted residue in panel 2
850 // to change, causing a scroll in panel 1 etc)
851 ap.setToScrollComplementPanel(false);
852 wasScrolled = ap.scrollToPosition(results);
855 seqCanvas.revalidate();
857 ap.setToScrollComplementPanel(true);
860 boolean noFastPaint = wasScrolled && av.getWrapAlignment();
861 if (seqCanvas.highlightSearchResults(results, noFastPaint))
863 setStatusMessage(results);
868 public VamsasSource getVamsasSource()
870 return this.ap == null ? null : this.ap.av;
874 public void updateColours(SequenceI seq, int index)
876 System.out.println("update the seqPanel colours");
881 * Action on mouse movement is to update the status bar to show the current
882 * sequence position, and (if features are shown) to show any features at the
883 * position in a tooltip. Does nothing if the mouse move does not change
889 public void mouseMoved(MouseEvent evt)
893 // This is because MacOSX creates a mouseMoved
894 // If control is down, other platforms will not.
898 final MousePos mousePos = findMousePosition(evt);
899 if (mousePos.equals(lastMousePosition))
902 * just a pixel move without change of 'cell'
906 lastMousePosition = mousePos;
908 if (mousePos.isOverAnnotation())
910 mouseMovedOverAnnotation(mousePos);
913 final int seq = mousePos.seqIndex;
915 final int column = mousePos.column;
916 if (column < 0 || seq < 0 || seq >= av.getAlignment().getHeight())
918 lastMousePosition = null;
919 setToolTipText(null);
921 ap.alignFrame.setStatus("");
925 SequenceI sequence = av.getAlignment().getSequenceAt(seq);
927 if (column >= sequence.getLength())
933 * set status bar message, returning residue position in sequence
935 boolean isGapped = Comparison.isGap(sequence.getCharAt(column));
936 final int pos = setStatusMessage(sequence, column, seq);
937 if (ssm != null && !isGapped)
939 mouseOverSequence(sequence, column, pos);
942 tooltipText.setLength(6); // Cuts the buffer back to <html>
944 SequenceGroup[] groups = av.getAlignment().findAllGroups(sequence);
947 for (int g = 0; g < groups.length; g++)
949 if (groups[g].getStartRes() <= column
950 && groups[g].getEndRes() >= column)
952 if (!groups[g].getName().startsWith("JTreeGroup")
953 && !groups[g].getName().startsWith("JGroup"))
955 tooltipText.append(groups[g].getName());
958 if (groups[g].getDescription() != null)
960 tooltipText.append(": " + groups[g].getDescription());
967 * add any features at the position to the tooltip; if over a gap, only
968 * add features that straddle the gap (pos may be the residue before or
971 if (av.isShowSequenceFeatures())
973 List<SequenceFeature> features = ap.getFeatureRenderer()
974 .findFeaturesAtColumn(sequence, column + 1);
975 seqARep.appendFeatures(tooltipText, pos, features,
976 this.ap.getSeqPanel().seqCanvas.fr);
978 if (tooltipText.length() == 6) // <html>
980 setToolTipText(null);
985 if (tooltipText.length() > MAX_TOOLTIP_LENGTH) // constant
987 tooltipText.setLength(MAX_TOOLTIP_LENGTH);
988 tooltipText.append("...");
990 String textString = tooltipText.toString();
991 if (lastTooltip == null || !lastTooltip.equals(textString))
993 String formattedTooltipText = JvSwingUtils.wrapTooltip(true,
995 setToolTipText(formattedTooltipText);
996 lastTooltip = textString;
1002 * When the view is in wrapped mode, and the mouse is over an annotation row,
1003 * shows the corresponding tooltip and status message (if any)
1008 protected void mouseMovedOverAnnotation(MousePos pos)
1010 final int column = pos.column;
1011 final int rowIndex = pos.annotationIndex;
1013 if (column < 0 || !av.getWrapAlignment() || !av.isShowAnnotation()
1018 AlignmentAnnotation[] anns = av.getAlignment().getAlignmentAnnotation();
1020 String tooltip = AnnotationPanel.buildToolTip(anns[rowIndex], column,
1022 setToolTipText(tooltip);
1023 lastTooltip = tooltip;
1025 String msg = AnnotationPanel.getStatusMessage(av.getAlignment(), column,
1027 ap.alignFrame.setStatus(msg);
1030 private Point lastp = null;
1035 * @see javax.swing.JComponent#getToolTipLocation(java.awt.event.MouseEvent)
1038 public Point getToolTipLocation(MouseEvent event)
1040 if (tooltipText == null || tooltipText.length() <= 6)
1046 int x = event.getX();
1048 // switch sides when tooltip is too close to edge
1049 int wdth = (w - x < 200) ? -(w / 2) : 5;
1051 if (!event.isShiftDown() || p == null)
1053 p = new Point(event.getX() + wdth, event.getY() - 20);
1057 * TODO: try to set position so region is not obscured by tooltip
1065 * set when the current UI interaction has resulted in a change that requires
1066 * shading in overviews and structures to be recalculated. this could be
1067 * changed to a something more expressive that indicates what actually has
1068 * changed, so selective redraws can be applied (ie. only structures, only
1071 private boolean updateOverviewAndStructs = false; // TODO: refactor to avcontroller
1074 * set if av.getSelectionGroup() refers to a group that is defined on the
1075 * alignment view, rather than a transient selection
1077 // private boolean editingDefinedGroup = false; // TODO: refactor to
1078 // avcontroller or viewModel
1081 * Sets the status message in alignment panel, showing the sequence number
1082 * (index) and id, and residue and residue position if not at a gap, for the
1083 * given sequence and column position. Returns the residue position returned
1084 * by Sequence.findPosition. Note this may be for the nearest adjacent residue
1085 * if at a gapped position.
1088 * aligned sequence object
1092 * index of sequence in alignment
1093 * @return sequence position of residue at column, or adjacent residue if at a
1096 int setStatusMessage(SequenceI sequence, final int column, int seqIndex)
1098 char sequenceChar = sequence.getCharAt(column);
1099 int pos = sequence.findPosition(column);
1100 setStatusMessage(sequence, seqIndex, sequenceChar, pos);
1106 * Builds the status message for the current cursor location and writes it to
1107 * the status bar, for example
1110 * Sequence 3 ID: FER1_SOLLC
1111 * Sequence 5 ID: FER1_PEA Residue: THR (4)
1112 * Sequence 5 ID: FER1_PEA Residue: B (3)
1113 * Sequence 6 ID: O.niloticus.3 Nucleotide: Uracil (2)
1118 * sequence position in the alignment (1..)
1119 * @param sequenceChar
1120 * the character under the cursor
1122 * the sequence residue position (if not over a gap)
1124 protected void setStatusMessage(SequenceI sequence, int seqIndex,
1125 char sequenceChar, int residuePos)
1127 StringBuilder text = new StringBuilder(32);
1130 * Sequence number (if known), and sequence name.
1132 String seqno = seqIndex == -1 ? "" : " " + (seqIndex + 1);
1133 text.append("Sequence").append(seqno).append(" ID: ")
1134 .append(sequence.getName());
1136 String residue = null;
1139 * Try to translate the display character to residue name (null for gap).
1141 boolean isGapped = Comparison.isGap(sequenceChar);
1145 boolean nucleotide = av.getAlignment().isNucleotide();
1146 String displayChar = String.valueOf(sequenceChar);
1149 residue = ResidueProperties.nucleotideName.get(displayChar);
1153 residue = "X".equalsIgnoreCase(displayChar) ? "X"
1154 : ("*".equals(displayChar) ? "STOP"
1155 : ResidueProperties.aa2Triplet.get(displayChar));
1157 text.append(" ").append(nucleotide ? "Nucleotide" : "Residue")
1158 .append(": ").append(residue == null ? displayChar : residue);
1160 text.append(" (").append(Integer.toString(residuePos)).append(")");
1162 ap.alignFrame.setStatus(text.toString());
1166 * Set the status bar message to highlight the first matched position in
1171 private void setStatusMessage(SearchResultsI results)
1173 AlignmentI al = this.av.getAlignment();
1174 int sequenceIndex = al.findIndex(results);
1175 if (sequenceIndex == -1)
1179 SequenceI ds = al.getSequenceAt(sequenceIndex).getDatasetSequence();
1180 for (SearchResultMatchI m : results.getResults())
1182 SequenceI seq = m.getSequence();
1183 if (seq.getDatasetSequence() != null)
1185 seq = seq.getDatasetSequence();
1190 int start = m.getStart();
1191 setStatusMessage(seq, sequenceIndex, seq.getCharAt(start - 1),
1202 public void mouseDragged(MouseEvent evt)
1204 MousePos pos = findMousePosition(evt);
1205 if (pos.isOverAnnotation() || pos.column == -1)
1210 if (mouseWheelPressed)
1212 boolean inSplitFrame = ap.av.getCodingComplement() != null;
1213 boolean copyChanges = inSplitFrame && av.isProteinFontAsCdna();
1215 int oldWidth = av.getCharWidth();
1217 // Which is bigger, left-right or up-down?
1218 if (Math.abs(evt.getY() - lastMousePress.getY()) > Math
1219 .abs(evt.getX() - lastMousePress.getX()))
1222 * on drag up or down, decrement or increment font size
1224 int fontSize = av.font.getSize();
1225 boolean fontChanged = false;
1227 if (evt.getY() < lastMousePress.getY())
1232 else if (evt.getY() > lastMousePress.getY())
1245 Font newFont = new Font(av.font.getName(), av.font.getStyle(),
1247 av.setFont(newFont, true);
1248 av.setCharWidth(oldWidth);
1252 ap.av.getCodingComplement().setFont(newFont, true);
1253 SplitFrame splitFrame = (SplitFrame) ap.alignFrame
1254 .getSplitViewContainer();
1255 splitFrame.adjustLayout();
1256 splitFrame.repaint();
1263 * on drag left or right, decrement or increment character width
1266 if (evt.getX() < lastMousePress.getX() && av.getCharWidth() > 1)
1268 newWidth = av.getCharWidth() - 1;
1269 av.setCharWidth(newWidth);
1271 else if (evt.getX() > lastMousePress.getX())
1273 newWidth = av.getCharWidth() + 1;
1274 av.setCharWidth(newWidth);
1278 ap.paintAlignment(false, false);
1282 * need to ensure newWidth is set on cdna, regardless of which
1283 * panel the mouse drag happened in; protein will compute its
1284 * character width as 1:1 or 3:1
1286 av.getCodingComplement().setCharWidth(newWidth);
1287 SplitFrame splitFrame = (SplitFrame) ap.alignFrame
1288 .getSplitViewContainer();
1289 splitFrame.adjustLayout();
1290 splitFrame.repaint();
1295 FontMetrics fm = getFontMetrics(av.getFont());
1296 av.validCharWidth = fm.charWidth('M') <= av.getCharWidth();
1298 lastMousePress = evt.getPoint();
1305 dragStretchGroup(evt);
1309 int res = pos.column;
1316 if ((editLastRes == -1) || (editLastRes == res))
1321 if ((res < av.getAlignment().getWidth()) && (res < editLastRes))
1323 // dragLeft, delete gap
1324 editSequence(false, false, res);
1328 editSequence(true, false, res);
1331 mouseDragging = true;
1332 if (scrollThread != null)
1334 scrollThread.setMousePosition(evt.getPoint());
1339 * Edits the sequence to insert or delete one or more gaps, in response to a
1340 * mouse drag or cursor mode command. The number of inserts/deletes may be
1341 * specified with the cursor command, or else depends on the mouse event
1342 * (normally one column, but potentially more for a fast mouse drag).
1344 * Delete gaps is limited to the number of gaps left of the cursor position
1345 * (mouse drag), or at or right of the cursor position (cursor mode).
1347 * In group editing mode (Ctrl or Cmd down), the edit acts on all sequences in
1348 * the current selection group.
1350 * In locked editing mode (with a selection group present), inserts/deletions
1351 * within the selection group are limited to its boundaries (and edits outside
1352 * the group stop at its border).
1355 * true to insert gaps, false to delete gaps
1357 * (unused parameter)
1359 * the column at which to perform the action; the number of columns
1360 * affected depends on <code>this.editLastRes</code> (cursor column
1363 synchronized void editSequence(boolean insertGap, boolean editSeq,
1367 int fixedRight = -1;
1368 boolean fixedColumns = false;
1369 SequenceGroup sg = av.getSelectionGroup();
1371 final SequenceI seq = av.getAlignment().getSequenceAt(editStartSeq);
1373 // No group, but the sequence may represent a group
1374 if (!groupEditing && av.hasHiddenRows())
1376 if (av.isHiddenRepSequence(seq))
1378 sg = av.getRepresentedSequences(seq);
1379 groupEditing = true;
1383 StringBuilder message = new StringBuilder(64); // for status bar
1386 * make a name for the edit action, for
1387 * status bar message and Undo/Redo menu
1389 String label = null;
1392 message.append("Edit group:");
1393 label = MessageManager.getString("action.edit_group");
1397 message.append("Edit sequence: " + seq.getName());
1398 label = seq.getName();
1399 if (label.length() > 10)
1401 label = label.substring(0, 10);
1403 label = MessageManager.formatMessage("label.edit_params",
1409 * initialise the edit command if there is not
1410 * already one being extended
1412 if (editCommand == null)
1414 editCommand = new EditCommand(label);
1419 message.append(" insert ");
1423 message.append(" delete ");
1426 message.append(Math.abs(startres - editLastRes) + " gaps.");
1427 ap.alignFrame.setStatus(message.toString());
1430 * is there a selection group containing the sequence being edited?
1431 * if so the boundary of the group is the limit of the edit
1432 * (but the edit may be inside or outside the selection group)
1434 boolean inSelectionGroup = sg != null
1435 && sg.getSequences(av.getHiddenRepSequences()).contains(seq);
1436 if (groupEditing || inSelectionGroup)
1438 fixedColumns = true;
1440 // sg might be null as the user may only see 1 sequence,
1441 // but the sequence represents a group
1444 if (!av.isHiddenRepSequence(seq))
1449 sg = av.getRepresentedSequences(seq);
1452 fixedLeft = sg.getStartRes();
1453 fixedRight = sg.getEndRes();
1455 if ((startres < fixedLeft && editLastRes >= fixedLeft)
1456 || (startres >= fixedLeft && editLastRes < fixedLeft)
1457 || (startres > fixedRight && editLastRes <= fixedRight)
1458 || (startres <= fixedRight && editLastRes > fixedRight))
1464 if (fixedLeft > startres)
1466 fixedRight = fixedLeft - 1;
1469 else if (fixedRight < startres)
1471 fixedLeft = fixedRight;
1476 if (av.hasHiddenColumns())
1478 fixedColumns = true;
1479 int y1 = av.getAlignment().getHiddenColumns()
1480 .getNextHiddenBoundary(true, startres);
1481 int y2 = av.getAlignment().getHiddenColumns()
1482 .getNextHiddenBoundary(false, startres);
1484 if ((insertGap && startres > y1 && editLastRes < y1)
1485 || (!insertGap && startres < y2 && editLastRes > y2))
1491 // System.out.print(y1+" "+y2+" "+fixedLeft+" "+fixedRight+"~~");
1492 // Selection spans a hidden region
1493 if (fixedLeft < y1 && (fixedRight > y2 || fixedRight == -1))
1501 fixedRight = y2 - 1;
1506 boolean success = doEditSequence(insertGap, editSeq, startres,
1507 fixedRight, fixedColumns, sg);
1510 * report what actually happened (might be less than
1511 * what was requested), by inspecting the edit commands added
1513 String msg = getEditStatusMessage(editCommand);
1514 ap.alignFrame.setStatus(msg == null ? " " : msg);
1520 editLastRes = startres;
1521 seqCanvas.repaint();
1525 * A helper method that performs the requested editing to insert or delete
1526 * gaps (if possible). Answers true if the edit was successful, false if could
1527 * only be performed in part or not at all. Failure may occur in 'locked edit'
1528 * mode, when an insertion requires a matching gapped position (or column) to
1529 * delete, and deletion requires an adjacent gapped position (or column) to
1533 * true if inserting gap(s), false if deleting
1535 * (unused parameter, currently always false)
1537 * the column at which to perform the edit
1539 * fixed right boundary column of a locked edit (within or to the
1540 * left of a selection group)
1541 * @param fixedColumns
1542 * true if this is a locked edit
1544 * the sequence group (if group edit is being performed)
1547 protected boolean doEditSequence(final boolean insertGap,
1548 final boolean editSeq, final int startres, int fixedRight,
1549 final boolean fixedColumns, final SequenceGroup sg)
1551 final SequenceI seq = av.getAlignment().getSequenceAt(editStartSeq);
1552 SequenceI[] seqs = new SequenceI[] { seq };
1556 List<SequenceI> vseqs = sg.getSequences(av.getHiddenRepSequences());
1557 int g, groupSize = vseqs.size();
1558 SequenceI[] groupSeqs = new SequenceI[groupSize];
1559 for (g = 0; g < groupSeqs.length; g++)
1561 groupSeqs[g] = vseqs.get(g);
1567 // If the user has selected the whole sequence, and is dragging to
1568 // the right, we can still extend the alignment and selectionGroup
1569 if (sg.getStartRes() == 0 && sg.getEndRes() == fixedRight
1570 && sg.getEndRes() == av.getAlignment().getWidth() - 1)
1573 av.getAlignment().getWidth() + startres - editLastRes);
1574 fixedRight = sg.getEndRes();
1577 // Is it valid with fixed columns??
1578 // Find the next gap before the end
1579 // of the visible region boundary
1580 boolean blank = false;
1581 for (; fixedRight > editLastRes; fixedRight--)
1585 for (g = 0; g < groupSize; g++)
1587 for (int j = 0; j < startres - editLastRes; j++)
1590 .isGap(groupSeqs[g].getCharAt(fixedRight - j)))
1605 if (sg.getSize() == av.getAlignment().getHeight())
1607 if ((av.hasHiddenColumns()
1608 && startres < av.getAlignment().getHiddenColumns()
1609 .getNextHiddenBoundary(false, startres)))
1614 int alWidth = av.getAlignment().getWidth();
1615 if (av.hasHiddenRows())
1617 int hwidth = av.getAlignment().getHiddenSequences()
1619 if (hwidth > alWidth)
1624 // We can still insert gaps if the selectionGroup
1625 // contains all the sequences
1626 sg.setEndRes(sg.getEndRes() + startres - editLastRes);
1627 fixedRight = alWidth + startres - editLastRes;
1637 else if (!insertGap)
1639 // / Are we able to delete?
1640 // ie are all columns blank?
1642 for (g = 0; g < groupSize; g++)
1644 for (int j = startres; j < editLastRes; j++)
1646 if (groupSeqs[g].getLength() <= j)
1651 if (!Comparison.isGap(groupSeqs[g].getCharAt(j)))
1653 // Not a gap, block edit not valid
1662 // dragging to the right
1663 if (fixedColumns && fixedRight != -1)
1665 for (int j = editLastRes; j < startres; j++)
1667 insertGap(j, groupSeqs, fixedRight);
1672 appendEdit(Action.INSERT_GAP, groupSeqs, startres,
1673 startres - editLastRes, false);
1678 // dragging to the left
1679 if (fixedColumns && fixedRight != -1)
1681 for (int j = editLastRes; j > startres; j--)
1683 deleteChar(startres, groupSeqs, fixedRight);
1688 appendEdit(Action.DELETE_GAP, groupSeqs, startres,
1689 editLastRes - startres, false);
1696 * editing a single sequence
1700 // dragging to the right
1701 if (fixedColumns && fixedRight != -1)
1703 for (int j = editLastRes; j < startres; j++)
1705 if (!insertGap(j, seqs, fixedRight))
1708 * e.g. cursor mode command specified
1709 * more inserts than are possible
1717 appendEdit(Action.INSERT_GAP, seqs, editLastRes,
1718 startres - editLastRes, false);
1725 // dragging to the left
1726 if (fixedColumns && fixedRight != -1)
1728 for (int j = editLastRes; j > startres; j--)
1730 if (!Comparison.isGap(seq.getCharAt(startres)))
1734 deleteChar(startres, seqs, fixedRight);
1739 // could be a keyboard edit trying to delete none gaps
1741 for (int m = startres; m < editLastRes; m++)
1743 if (!Comparison.isGap(seq.getCharAt(m)))
1751 appendEdit(Action.DELETE_GAP, seqs, startres, max, false);
1756 {// insertGap==false AND editSeq==TRUE;
1757 if (fixedColumns && fixedRight != -1)
1759 for (int j = editLastRes; j < startres; j++)
1761 insertGap(j, seqs, fixedRight);
1766 appendEdit(Action.INSERT_NUC, seqs, editLastRes,
1767 startres - editLastRes, false);
1777 * Constructs an informative status bar message while dragging to insert or
1778 * delete gaps. Answers null if inserts and deletes cancel out.
1780 * @param editCommand
1781 * a command containing the list of individual edits
1784 protected static String getEditStatusMessage(EditCommand editCommand)
1786 if (editCommand == null)
1792 * add any inserts, and subtract any deletes,
1793 * not counting those auto-inserted when doing a 'locked edit'
1794 * (so only counting edits 'under the cursor')
1797 for (Edit cmd : editCommand.getEdits())
1799 if (!cmd.isSystemGenerated())
1801 count += cmd.getAction() == Action.INSERT_GAP ? cmd.getNumber()
1809 * inserts and deletes cancel out
1814 String msgKey = count > 1 ? "label.insert_gaps"
1815 : (count == 1 ? "label.insert_gap"
1816 : (count == -1 ? "label.delete_gap"
1817 : "label.delete_gaps"));
1818 count = Math.abs(count);
1820 return MessageManager.formatMessage(msgKey, String.valueOf(count));
1824 * Inserts one gap at column j, deleting the right-most gapped column up to
1825 * (and including) fixedColumn. Returns true if the edit is successful, false
1826 * if no blank column is available to allow the insertion to be balanced by a
1831 * @param fixedColumn
1834 boolean insertGap(int j, SequenceI[] seq, int fixedColumn)
1836 int blankColumn = fixedColumn;
1837 for (int s = 0; s < seq.length; s++)
1839 // Find the next gap before the end of the visible region boundary
1840 // If lastCol > j, theres a boundary after the gap insertion
1842 for (blankColumn = fixedColumn; blankColumn > j; blankColumn--)
1844 if (Comparison.isGap(seq[s].getCharAt(blankColumn)))
1846 // Theres a space, so break and insert the gap
1851 if (blankColumn <= j)
1853 blankColumn = fixedColumn;
1859 appendEdit(Action.DELETE_GAP, seq, blankColumn, 1, true);
1861 appendEdit(Action.INSERT_GAP, seq, j, 1, false);
1867 * Helper method to add and perform one edit action
1873 * @param systemGenerated
1874 * true if the edit is a 'balancing' delete (or insert) to match a
1875 * user's insert (or delete) in a locked editing region
1877 protected void appendEdit(Action action, SequenceI[] seq, int pos,
1878 int count, boolean systemGenerated)
1881 final Edit edit = new EditCommand().new Edit(action, seq, pos, count,
1882 av.getAlignment().getGapCharacter());
1883 edit.setSystemGenerated(systemGenerated);
1885 editCommand.appendEdit(edit, av.getAlignment(), true, null);
1889 * Deletes the character at column j, and inserts a gap at fixedColumn, in
1890 * each of the given sequences. The caller should ensure that all sequences
1891 * are gapped in column j.
1895 * @param fixedColumn
1897 void deleteChar(int j, SequenceI[] seqs, int fixedColumn)
1899 appendEdit(Action.DELETE_GAP, seqs, j, 1, false);
1901 appendEdit(Action.INSERT_GAP, seqs, fixedColumn, 1, true);
1905 * On reentering the panel, stops any scrolling that was started on dragging
1911 public void mouseEntered(MouseEvent e)
1921 * On leaving the panel, if the mouse is being dragged, starts a thread to
1922 * scroll it until the mouse is released (in unwrapped mode only)
1927 public void mouseExited(MouseEvent e)
1929 lastMousePosition = null;
1930 ap.alignFrame.setStatus(" ");
1931 if (av.getWrapAlignment())
1936 if (mouseDragging && scrollThread == null)
1938 scrollThread = new ScrollThread();
1943 * Handler for double-click on a position with one or more sequence features.
1944 * Opens the Amend Features dialog to allow feature details to be amended, or
1945 * the feature deleted.
1948 public void mouseClicked(MouseEvent evt)
1950 SequenceGroup sg = null;
1951 MousePos pos = findMousePosition(evt);
1952 if (pos.isOverAnnotation() || pos.seqIndex == -1 || pos.column == -1)
1957 if (evt.getClickCount() > 1)
1959 sg = av.getSelectionGroup();
1960 if (sg != null && sg.getSize() == 1
1961 && sg.getEndRes() - sg.getStartRes() < 2)
1963 av.setSelectionGroup(null);
1966 int column = pos.column;
1969 * find features at the position (if not gapped), or straddling
1970 * the position (if at a gap)
1972 SequenceI sequence = av.getAlignment().getSequenceAt(pos.seqIndex);
1973 List<SequenceFeature> features = seqCanvas.getFeatureRenderer()
1974 .findFeaturesAtColumn(sequence, column + 1);
1976 if (!features.isEmpty())
1979 * highlight the first feature at the position on the alignment
1981 SearchResultsI highlight = new SearchResults();
1982 highlight.addResult(sequence, features.get(0).getBegin(), features
1984 seqCanvas.highlightSearchResults(highlight, false);
1987 * open the Amend Features dialog; clear highlighting afterwards,
1988 * whether changes were made or not
1990 List<SequenceI> seqs = Collections.singletonList(sequence);
1991 seqCanvas.getFeatureRenderer().amendFeatures(seqs, features, false,
1993 av.setSearchResults(null); // clear highlighting
1994 seqCanvas.repaint(); // draw new/amended features
2000 * Responds to a mouse wheel movement by scrolling the alignment
2002 * <li>left or right, if the shift key is down, else up or down</li>
2003 * <li>right (or down) if the reported mouse movement is positive</li>
2004 * <li>left (or up) if the reported mouse movement is negative</li>
2006 * Note that this method may also be fired by scrolling with a gesture on a
2010 public void mouseWheelMoved(MouseWheelEvent e)
2013 double wheelRotation = e.getPreciseWheelRotation();
2016 * scroll more for large (fast) mouse movements
2018 int size = 1 + (int) Math.abs(wheelRotation);
2020 if (wheelRotation > 0)
2022 if (e.isShiftDown())
2026 * stop trying to scroll right when limit is reached (saves
2027 * expensive calls to Alignment.getWidth())
2029 while (size-- > 0 && !ap.isScrolledFullyRight())
2031 if (!av.getRanges().scrollRight(true))
2044 if (!av.getRanges().scrollUp(false))
2051 else if (wheelRotation < 0)
2053 if (e.isShiftDown())
2060 if (!av.getRanges().scrollRight(false))
2073 if (!av.getRanges().scrollUp(true))
2082 * update status bar and tooltip for new position
2083 * (need to synthesize a mouse movement to refresh tooltip)
2086 ToolTipManager.sharedInstance().mouseMoved(e);
2095 protected void doMousePressedDefineMode(MouseEvent evt, MousePos pos)
2097 if (pos.isOverAnnotation() || pos.seqIndex == -1 || pos.column == -1)
2102 final int res = pos.column;
2103 final int seq = pos.seqIndex;
2105 updateOverviewAndStructs = false;
2107 startWrapBlock = wrappedBlock;
2109 SequenceI sequence = av.getAlignment().getSequenceAt(seq);
2111 if ((sequence == null) || (res > sequence.getLength()))
2116 stretchGroup = av.getSelectionGroup();
2118 if (stretchGroup == null || !stretchGroup.contains(sequence, res))
2120 stretchGroup = av.getAlignment().findGroup(sequence, res);
2121 if (stretchGroup != null)
2123 // only update the current selection if the popup menu has a group to
2125 av.setSelectionGroup(stretchGroup);
2129 if (evt.isPopupTrigger()) // Mac: mousePressed
2131 showPopupMenu(evt, pos);
2136 * defer right-mouse click handling to mouseReleased on Windows
2137 * (where isPopupTrigger() will answer true)
2138 * NB isRightMouseButton is also true for Cmd-click on Mac
2140 if (SwingUtilities.isRightMouseButton(evt) && !Platform.isAMac())
2147 seqCanvas.cursorX = res;
2148 seqCanvas.cursorY = seq;
2149 seqCanvas.repaint();
2153 if (stretchGroup == null)
2155 createStretchGroup(res, sequence);
2158 if (stretchGroup != null)
2160 stretchGroup.addPropertyChangeListener(seqCanvas);
2163 seqCanvas.repaint();
2166 private void createStretchGroup(int res, SequenceI sequence)
2168 // Only if left mouse button do we want to change group sizes
2169 // define a new group here
2170 SequenceGroup sg = new SequenceGroup();
2171 sg.setStartRes(res);
2173 sg.addSequence(sequence, false);
2174 av.setSelectionGroup(sg);
2177 if (av.getConservationSelected())
2179 SliderPanel.setConservationSlider(ap, av.getResidueShading(),
2183 if (av.getAbovePIDThreshold())
2185 SliderPanel.setPIDSliderSource(ap, av.getResidueShading(),
2188 // TODO: stretchGroup will always be not null. Is this a merge error ?
2189 // or is there a threading issue here?
2190 if ((stretchGroup != null) && (stretchGroup.getEndRes() == res))
2192 // Edit end res position of selected group
2193 changeEndRes = true;
2195 else if ((stretchGroup != null) && (stretchGroup.getStartRes() == res))
2197 // Edit end res position of selected group
2198 changeStartRes = true;
2200 stretchGroup.getWidth();
2205 * Build and show a pop-up menu at the right-click mouse position
2210 void showPopupMenu(MouseEvent evt, MousePos pos)
2212 final int column = pos.column;
2213 final int seq = pos.seqIndex;
2214 SequenceI sequence = av.getAlignment().getSequenceAt(seq);
2215 List<SequenceFeature> features = ap.getFeatureRenderer()
2216 .findFeaturesAtColumn(sequence, column + 1);
2218 PopupMenu pop = new PopupMenu(ap, null, features);
2219 pop.show(this, evt.getX(), evt.getY());
2223 * Update the display after mouse up on a selection or group
2226 * mouse released event details
2228 * true if this event is happening after a mouse drag (rather than a
2231 protected void doMouseReleasedDefineMode(MouseEvent evt,
2234 if (stretchGroup == null)
2239 stretchGroup.removePropertyChangeListener(seqCanvas);
2241 // always do this - annotation has own state
2242 // but defer colourscheme update until hidden sequences are passed in
2243 boolean vischange = stretchGroup.recalcConservation(true);
2244 updateOverviewAndStructs |= vischange && av.isSelectionDefinedGroup()
2246 if (stretchGroup.cs != null)
2250 stretchGroup.cs.alignmentChanged(stretchGroup,
2251 av.getHiddenRepSequences());
2254 ResidueShaderI groupColourScheme = stretchGroup
2255 .getGroupColourScheme();
2256 String name = stretchGroup.getName();
2257 if (stretchGroup.cs.conservationApplied())
2259 SliderPanel.setConservationSlider(ap, groupColourScheme, name);
2261 if (stretchGroup.cs.getThreshold() > 0)
2263 SliderPanel.setPIDSliderSource(ap, groupColourScheme, name);
2266 PaintRefresher.Refresh(this, av.getSequenceSetId());
2267 // TODO: structure colours only need updating if stretchGroup used to or now
2268 // does contain sequences with structure views
2269 ap.paintAlignment(updateOverviewAndStructs, updateOverviewAndStructs);
2270 updateOverviewAndStructs = false;
2271 changeEndRes = false;
2272 changeStartRes = false;
2273 stretchGroup = null;
2278 * Resizes the borders of a selection group depending on the direction of
2283 protected void dragStretchGroup(MouseEvent evt)
2285 if (stretchGroup == null)
2290 MousePos pos = findMousePosition(evt);
2291 if (pos.isOverAnnotation() || pos.column == -1 || pos.seqIndex == -1)
2296 int res = pos.column;
2297 int y = pos.seqIndex;
2299 if (wrappedBlock != startWrapBlock)
2304 res = Math.min(res, av.getAlignment().getWidth()-1);
2306 if (stretchGroup.getEndRes() == res)
2308 // Edit end res position of selected group
2309 changeEndRes = true;
2311 else if (stretchGroup.getStartRes() == res)
2313 // Edit start res position of selected group
2314 changeStartRes = true;
2317 if (res < av.getRanges().getStartRes())
2319 res = av.getRanges().getStartRes();
2324 if (res > (stretchGroup.getStartRes() - 1))
2326 stretchGroup.setEndRes(res);
2327 updateOverviewAndStructs |= av.isSelectionDefinedGroup();
2330 else if (changeStartRes)
2332 if (res < (stretchGroup.getEndRes() + 1))
2334 stretchGroup.setStartRes(res);
2335 updateOverviewAndStructs |= av.isSelectionDefinedGroup();
2339 int dragDirection = 0;
2345 else if (y < oldSeq)
2350 while ((y != oldSeq) && (oldSeq > -1)
2351 && (y < av.getAlignment().getHeight()))
2353 // This routine ensures we don't skip any sequences, as the
2354 // selection is quite slow.
2355 Sequence seq = (Sequence) av.getAlignment().getSequenceAt(oldSeq);
2357 oldSeq += dragDirection;
2364 Sequence nextSeq = (Sequence) av.getAlignment().getSequenceAt(oldSeq);
2366 if (stretchGroup.getSequences(null).contains(nextSeq))
2368 stretchGroup.deleteSequence(seq, false);
2369 updateOverviewAndStructs |= av.isSelectionDefinedGroup();
2375 stretchGroup.addSequence(seq, false);
2378 stretchGroup.addSequence(nextSeq, false);
2379 updateOverviewAndStructs |= av.isSelectionDefinedGroup();
2388 mouseDragging = true;
2390 if (scrollThread != null)
2392 scrollThread.setMousePosition(evt.getPoint());
2396 * construct a status message showing the range of the selection
2398 StringBuilder status = new StringBuilder(64);
2399 List<SequenceI> seqs = stretchGroup.getSequences();
2400 String name = seqs.get(0).getName();
2401 if (name.length() > 20)
2403 name = name.substring(0, 20);
2405 status.append(name).append(" - ");
2406 name = seqs.get(seqs.size() - 1).getName();
2407 if (name.length() > 20)
2409 name = name.substring(0, 20);
2411 status.append(name).append(" ");
2412 int startRes = stretchGroup.getStartRes();
2413 status.append(" cols ").append(String.valueOf(startRes + 1))
2415 int endRes = stretchGroup.getEndRes();
2416 status.append(String.valueOf(endRes + 1));
2417 status.append(" (").append(String.valueOf(seqs.size())).append(" x ")
2418 .append(String.valueOf(endRes - startRes + 1)).append(")");
2419 ap.alignFrame.setStatus(status.toString());
2423 * Stops the scroll thread if it is running
2425 void stopScrolling()
2427 if (scrollThread != null)
2429 scrollThread.stopScrolling();
2430 scrollThread = null;
2432 mouseDragging = false;
2436 * Starts a thread to scroll the alignment, towards a given mouse position
2437 * outside the panel bounds
2441 void startScrolling(Point mousePos)
2443 if (scrollThread == null)
2445 scrollThread = new ScrollThread();
2448 mouseDragging = true;
2449 scrollThread.setMousePosition(mousePos);
2453 * Performs scrolling of the visible alignment left, right, up or down
2455 class ScrollThread extends Thread
2457 private Point mousePos;
2459 private volatile boolean threadRunning = true;
2464 public ScrollThread()
2466 setName("SeqPanel$ScrollThread");
2471 * Sets the position of the mouse that determines the direction of the
2476 public void setMousePosition(Point p)
2482 * Sets a flag that will cause the thread to exit
2484 public void stopScrolling()
2486 threadRunning = false;
2490 * Scrolls the alignment left or right, and/or up or down, depending on the
2491 * last notified mouse position, until the limit of the alignment is
2492 * reached, or a flag is set to stop the scroll
2497 while (threadRunning && mouseDragging)
2499 if (mousePos != null)
2501 boolean scrolled = false;
2502 ViewportRanges ranges = SeqPanel.this.av.getRanges();
2509 // mouse is above this panel - try scroll up
2510 scrolled = ranges.scrollUp(true);
2512 else if (mousePos.y >= getHeight())
2514 // mouse is below this panel - try scroll down
2515 scrolled = ranges.scrollUp(false);
2519 * scroll left or right
2523 scrolled |= ranges.scrollRight(false);
2525 else if (mousePos.x >= getWidth())
2527 scrolled |= ranges.scrollRight(true);
2532 * we have reached the limit of the visible alignment - quit
2534 threadRunning = false;
2535 SeqPanel.this.ap.repaint();
2542 } catch (Exception ex)
2550 * modify current selection according to a received message.
2553 public void selection(SequenceGroup seqsel, ColumnSelection colsel,
2554 HiddenColumns hidden, SelectionSource source)
2556 // TODO: fix this hack - source of messages is align viewport, but SeqPanel
2557 // handles selection messages...
2558 // TODO: extend config options to allow user to control if selections may be
2559 // shared between viewports.
2560 boolean iSentTheSelection = (av == source
2561 || (source instanceof AlignViewport
2562 && ((AlignmentViewport) source).getSequenceSetId()
2563 .equals(av.getSequenceSetId())));
2565 if (iSentTheSelection)
2567 // respond to our own event by updating dependent dialogs
2568 if (ap.getCalculationDialog() != null)
2570 ap.getCalculationDialog().validateCalcTypes();
2576 // process further ?
2577 if (!av.followSelection)
2583 * Ignore the selection if there is one of our own pending.
2585 if (av.isSelectionGroupChanged(false) || av.isColSelChanged(false))
2591 * Check for selection in a view of which this one is a dna/protein
2594 if (selectionFromTranslation(seqsel, colsel, hidden, source))
2599 // do we want to thread this ? (contention with seqsel and colsel locks, I
2602 * only copy colsel if there is a real intersection between
2603 * sequence selection and this panel's alignment
2605 boolean repaint = false;
2606 boolean copycolsel = false;
2608 SequenceGroup sgroup = null;
2609 if (seqsel != null && seqsel.getSize() > 0)
2611 if (av.getAlignment() == null)
2613 Cache.log.warn("alignviewport av SeqSetId=" + av.getSequenceSetId()
2614 + " ViewId=" + av.getViewId()
2615 + " 's alignment is NULL! returning immediately.");
2618 sgroup = seqsel.intersect(av.getAlignment(),
2619 (av.hasHiddenRows()) ? av.getHiddenRepSequences() : null);
2620 if ((sgroup != null && sgroup.getSize() > 0))
2625 if (sgroup != null && sgroup.getSize() > 0)
2627 av.setSelectionGroup(sgroup);
2631 av.setSelectionGroup(null);
2633 av.isSelectionGroupChanged(true);
2638 // the current selection is unset or from a previous message
2639 // so import the new colsel.
2640 if (colsel == null || colsel.isEmpty())
2642 if (av.getColumnSelection() != null)
2644 av.getColumnSelection().clear();
2650 // TODO: shift colSel according to the intersecting sequences
2651 if (av.getColumnSelection() == null)
2653 av.setColumnSelection(new ColumnSelection(colsel));
2657 av.getColumnSelection().setElementsFrom(colsel,
2658 av.getAlignment().getHiddenColumns());
2661 av.isColSelChanged(true);
2665 if (copycolsel && av.hasHiddenColumns()
2666 && (av.getAlignment().getHiddenColumns() == null))
2668 System.err.println("Bad things");
2670 if (repaint) // always true!
2672 // probably finessing with multiple redraws here
2673 PaintRefresher.Refresh(this, av.getSequenceSetId());
2674 // ap.paintAlignment(false);
2677 // lastly, update dependent dialogs
2678 if (ap.getCalculationDialog() != null)
2680 ap.getCalculationDialog().validateCalcTypes();
2686 * If this panel is a cdna/protein translation view of the selection source,
2687 * tries to map the source selection to a local one, and returns true. Else
2694 protected boolean selectionFromTranslation(SequenceGroup seqsel,
2695 ColumnSelection colsel, HiddenColumns hidden,
2696 SelectionSource source)
2698 if (!(source instanceof AlignViewportI))
2702 final AlignViewportI sourceAv = (AlignViewportI) source;
2703 if (sourceAv.getCodingComplement() != av
2704 && av.getCodingComplement() != sourceAv)
2710 * Map sequence selection
2712 SequenceGroup sg = MappingUtils.mapSequenceGroup(seqsel, sourceAv, av);
2713 av.setSelectionGroup(sg);
2714 av.isSelectionGroupChanged(true);
2717 * Map column selection
2719 // ColumnSelection cs = MappingUtils.mapColumnSelection(colsel, sourceAv,
2721 ColumnSelection cs = new ColumnSelection();
2722 HiddenColumns hs = new HiddenColumns();
2723 MappingUtils.mapColumnSelection(colsel, hidden, sourceAv, av, cs, hs);
2724 av.setColumnSelection(cs);
2725 boolean hiddenChanged = av.getAlignment().setHiddenColumns(hs);
2727 // lastly, update any dependent dialogs
2728 if (ap.getCalculationDialog() != null)
2730 ap.getCalculationDialog().validateCalcTypes();
2734 * repaint alignment, and also Overview or Structure
2735 * if hidden column selection has changed
2737 ap.paintAlignment(hiddenChanged, hiddenChanged);
2744 * @return null or last search results handled by this panel
2746 public SearchResultsI getLastSearchResults()
2748 return lastSearchResults;