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 public void mouseWheelMoved(MouseWheelEvent e)
2003 double wheelRotation = e.getPreciseWheelRotation();
2004 if (wheelRotation > 0)
2006 if (e.isShiftDown())
2008 av.getRanges().scrollRight(true);
2013 av.getRanges().scrollUp(false);
2016 else if (wheelRotation < 0)
2018 if (e.isShiftDown())
2020 av.getRanges().scrollRight(false);
2024 av.getRanges().scrollUp(true);
2029 * update status bar and tooltip for new position
2030 * (need to synthesize a mouse movement to refresh tooltip)
2033 ToolTipManager.sharedInstance().mouseMoved(e);
2042 protected void doMousePressedDefineMode(MouseEvent evt, MousePos pos)
2044 if (pos.isOverAnnotation() || pos.seqIndex == -1 || pos.column == -1)
2049 final int res = pos.column;
2050 final int seq = pos.seqIndex;
2052 updateOverviewAndStructs = false;
2054 startWrapBlock = wrappedBlock;
2056 SequenceI sequence = av.getAlignment().getSequenceAt(seq);
2058 if ((sequence == null) || (res > sequence.getLength()))
2063 stretchGroup = av.getSelectionGroup();
2065 if (stretchGroup == null || !stretchGroup.contains(sequence, res))
2067 stretchGroup = av.getAlignment().findGroup(sequence, res);
2068 if (stretchGroup != null)
2070 // only update the current selection if the popup menu has a group to
2072 av.setSelectionGroup(stretchGroup);
2076 if (evt.isPopupTrigger()) // Mac: mousePressed
2078 showPopupMenu(evt, pos);
2083 * defer right-mouse click handling to mouseReleased on Windows
2084 * (where isPopupTrigger() will answer true)
2085 * NB isRightMouseButton is also true for Cmd-click on Mac
2087 if (SwingUtilities.isRightMouseButton(evt) && !Platform.isAMac())
2094 seqCanvas.cursorX = res;
2095 seqCanvas.cursorY = seq;
2096 seqCanvas.repaint();
2100 if (stretchGroup == null)
2102 createStretchGroup(res, sequence);
2105 if (stretchGroup != null)
2107 stretchGroup.addPropertyChangeListener(seqCanvas);
2110 seqCanvas.repaint();
2113 private void createStretchGroup(int res, SequenceI sequence)
2115 // Only if left mouse button do we want to change group sizes
2116 // define a new group here
2117 SequenceGroup sg = new SequenceGroup();
2118 sg.setStartRes(res);
2120 sg.addSequence(sequence, false);
2121 av.setSelectionGroup(sg);
2124 if (av.getConservationSelected())
2126 SliderPanel.setConservationSlider(ap, av.getResidueShading(),
2130 if (av.getAbovePIDThreshold())
2132 SliderPanel.setPIDSliderSource(ap, av.getResidueShading(),
2135 // TODO: stretchGroup will always be not null. Is this a merge error ?
2136 // or is there a threading issue here?
2137 if ((stretchGroup != null) && (stretchGroup.getEndRes() == res))
2139 // Edit end res position of selected group
2140 changeEndRes = true;
2142 else if ((stretchGroup != null) && (stretchGroup.getStartRes() == res))
2144 // Edit end res position of selected group
2145 changeStartRes = true;
2147 stretchGroup.getWidth();
2152 * Build and show a pop-up menu at the right-click mouse position
2157 void showPopupMenu(MouseEvent evt, MousePos pos)
2159 final int column = pos.column;
2160 final int seq = pos.seqIndex;
2161 SequenceI sequence = av.getAlignment().getSequenceAt(seq);
2162 List<SequenceFeature> features = ap.getFeatureRenderer()
2163 .findFeaturesAtColumn(sequence, column + 1);
2165 PopupMenu pop = new PopupMenu(ap, null, features);
2166 pop.show(this, evt.getX(), evt.getY());
2170 * Update the display after mouse up on a selection or group
2173 * mouse released event details
2175 * true if this event is happening after a mouse drag (rather than a
2178 protected void doMouseReleasedDefineMode(MouseEvent evt,
2181 if (stretchGroup == null)
2186 stretchGroup.removePropertyChangeListener(seqCanvas);
2188 // always do this - annotation has own state
2189 // but defer colourscheme update until hidden sequences are passed in
2190 boolean vischange = stretchGroup.recalcAnnotations(true);
2191 updateOverviewAndStructs |= vischange && av.isSelectionDefinedGroup()
2193 if (stretchGroup.cs != null)
2197 stretchGroup.cs.alignmentChanged(stretchGroup,
2198 av.getHiddenRepSequences());
2201 ResidueShaderI groupColourScheme = stretchGroup
2202 .getGroupColourScheme();
2203 String name = stretchGroup.getName();
2204 if (stretchGroup.cs.conservationApplied())
2206 SliderPanel.setConservationSlider(ap, groupColourScheme, name);
2208 if (stretchGroup.cs.getThreshold() > 0)
2210 SliderPanel.setPIDSliderSource(ap, groupColourScheme, name);
2213 PaintRefresher.Refresh(this, av.getSequenceSetId());
2214 // TODO: structure colours only need updating if stretchGroup used to or now
2215 // does contain sequences with structure views
2216 ap.paintAlignment(updateOverviewAndStructs, updateOverviewAndStructs);
2217 updateOverviewAndStructs = false;
2218 changeEndRes = false;
2219 changeStartRes = false;
2220 stretchGroup = null;
2225 * Resizes the borders of a selection group depending on the direction of
2230 protected void dragStretchGroup(MouseEvent evt)
2232 if (stretchGroup == null)
2237 MousePos pos = findMousePosition(evt);
2238 if (pos.isOverAnnotation() || pos.column == -1 || pos.seqIndex == -1)
2243 int res = pos.column;
2244 int y = pos.seqIndex;
2246 if (wrappedBlock != startWrapBlock)
2251 res = Math.min(res, av.getAlignment().getWidth()-1);
2253 if (stretchGroup.getEndRes() == res)
2255 // Edit end res position of selected group
2256 changeEndRes = true;
2258 else if (stretchGroup.getStartRes() == res)
2260 // Edit start res position of selected group
2261 changeStartRes = true;
2264 if (res < av.getRanges().getStartRes())
2266 res = av.getRanges().getStartRes();
2271 if (res > (stretchGroup.getStartRes() - 1))
2273 stretchGroup.setEndRes(res);
2274 updateOverviewAndStructs |= av.isSelectionDefinedGroup();
2277 else if (changeStartRes)
2279 if (res < (stretchGroup.getEndRes() + 1))
2281 stretchGroup.setStartRes(res);
2282 updateOverviewAndStructs |= av.isSelectionDefinedGroup();
2286 int dragDirection = 0;
2292 else if (y < oldSeq)
2297 while ((y != oldSeq) && (oldSeq > -1)
2298 && (y < av.getAlignment().getHeight()))
2300 // This routine ensures we don't skip any sequences, as the
2301 // selection is quite slow.
2302 Sequence seq = (Sequence) av.getAlignment().getSequenceAt(oldSeq);
2304 oldSeq += dragDirection;
2311 Sequence nextSeq = (Sequence) av.getAlignment().getSequenceAt(oldSeq);
2313 if (stretchGroup.getSequences(null).contains(nextSeq))
2315 stretchGroup.deleteSequence(seq, false);
2316 updateOverviewAndStructs |= av.isSelectionDefinedGroup();
2322 stretchGroup.addSequence(seq, false);
2325 stretchGroup.addSequence(nextSeq, false);
2326 updateOverviewAndStructs |= av.isSelectionDefinedGroup();
2335 mouseDragging = true;
2337 if (scrollThread != null)
2339 scrollThread.setMousePosition(evt.getPoint());
2343 * construct a status message showing the range of the selection
2345 StringBuilder status = new StringBuilder(64);
2346 List<SequenceI> seqs = stretchGroup.getSequences();
2347 String name = seqs.get(0).getName();
2348 if (name.length() > 20)
2350 name = name.substring(0, 20);
2352 status.append(name).append(" - ");
2353 name = seqs.get(seqs.size() - 1).getName();
2354 if (name.length() > 20)
2356 name = name.substring(0, 20);
2358 status.append(name).append(" ");
2359 int startRes = stretchGroup.getStartRes();
2360 status.append(" cols ").append(String.valueOf(startRes + 1))
2362 int endRes = stretchGroup.getEndRes();
2363 status.append(String.valueOf(endRes + 1));
2364 status.append(" (").append(String.valueOf(seqs.size())).append(" x ")
2365 .append(String.valueOf(endRes - startRes + 1)).append(")");
2366 ap.alignFrame.setStatus(status.toString());
2370 * Stops the scroll thread if it is running
2372 void stopScrolling()
2374 if (scrollThread != null)
2376 scrollThread.stopScrolling();
2377 scrollThread = null;
2379 mouseDragging = false;
2383 * Starts a thread to scroll the alignment, towards a given mouse position
2384 * outside the panel bounds
2388 void startScrolling(Point mousePos)
2390 if (scrollThread == null)
2392 scrollThread = new ScrollThread();
2395 mouseDragging = true;
2396 scrollThread.setMousePosition(mousePos);
2400 * Performs scrolling of the visible alignment left, right, up or down
2402 class ScrollThread extends Thread
2404 private Point mousePos;
2406 private volatile boolean threadRunning = true;
2411 public ScrollThread()
2413 setName("SeqPanel$ScrollThread");
2418 * Sets the position of the mouse that determines the direction of the
2423 public void setMousePosition(Point p)
2429 * Sets a flag that will cause the thread to exit
2431 public void stopScrolling()
2433 threadRunning = false;
2437 * Scrolls the alignment left or right, and/or up or down, depending on the
2438 * last notified mouse position, until the limit of the alignment is
2439 * reached, or a flag is set to stop the scroll
2444 while (threadRunning && mouseDragging)
2446 if (mousePos != null)
2448 boolean scrolled = false;
2449 ViewportRanges ranges = SeqPanel.this.av.getRanges();
2456 // mouse is above this panel - try scroll up
2457 scrolled = ranges.scrollUp(true);
2459 else if (mousePos.y >= getHeight())
2461 // mouse is below this panel - try scroll down
2462 scrolled = ranges.scrollUp(false);
2466 * scroll left or right
2470 scrolled |= ranges.scrollRight(false);
2472 else if (mousePos.x >= getWidth())
2474 scrolled |= ranges.scrollRight(true);
2479 * we have reached the limit of the visible alignment - quit
2481 threadRunning = false;
2482 SeqPanel.this.ap.repaint();
2489 } catch (Exception ex)
2497 * modify current selection according to a received message.
2500 public void selection(SequenceGroup seqsel, ColumnSelection colsel,
2501 HiddenColumns hidden, SelectionSource source)
2503 // TODO: fix this hack - source of messages is align viewport, but SeqPanel
2504 // handles selection messages...
2505 // TODO: extend config options to allow user to control if selections may be
2506 // shared between viewports.
2507 boolean iSentTheSelection = (av == source
2508 || (source instanceof AlignViewport
2509 && ((AlignmentViewport) source).getSequenceSetId()
2510 .equals(av.getSequenceSetId())));
2512 if (iSentTheSelection)
2514 // respond to our own event by updating dependent dialogs
2515 if (ap.getCalculationDialog() != null)
2517 ap.getCalculationDialog().validateCalcTypes();
2523 // process further ?
2524 if (!av.followSelection)
2530 * Ignore the selection if there is one of our own pending.
2532 if (av.isSelectionGroupChanged(false) || av.isColSelChanged(false))
2538 * Check for selection in a view of which this one is a dna/protein
2541 if (selectionFromTranslation(seqsel, colsel, hidden, source))
2546 // do we want to thread this ? (contention with seqsel and colsel locks, I
2549 * only copy colsel if there is a real intersection between
2550 * sequence selection and this panel's alignment
2552 boolean repaint = false;
2553 boolean copycolsel = false;
2555 SequenceGroup sgroup = null;
2556 if (seqsel != null && seqsel.getSize() > 0)
2558 if (av.getAlignment() == null)
2560 Cache.log.warn("alignviewport av SeqSetId=" + av.getSequenceSetId()
2561 + " ViewId=" + av.getViewId()
2562 + " 's alignment is NULL! returning immediately.");
2565 sgroup = seqsel.intersect(av.getAlignment(),
2566 (av.hasHiddenRows()) ? av.getHiddenRepSequences() : null);
2567 if ((sgroup != null && sgroup.getSize() > 0))
2572 if (sgroup != null && sgroup.getSize() > 0)
2574 av.setSelectionGroup(sgroup);
2578 av.setSelectionGroup(null);
2580 av.isSelectionGroupChanged(true);
2585 // the current selection is unset or from a previous message
2586 // so import the new colsel.
2587 if (colsel == null || colsel.isEmpty())
2589 if (av.getColumnSelection() != null)
2591 av.getColumnSelection().clear();
2597 // TODO: shift colSel according to the intersecting sequences
2598 if (av.getColumnSelection() == null)
2600 av.setColumnSelection(new ColumnSelection(colsel));
2604 av.getColumnSelection().setElementsFrom(colsel,
2605 av.getAlignment().getHiddenColumns());
2608 av.isColSelChanged(true);
2612 if (copycolsel && av.hasHiddenColumns()
2613 && (av.getAlignment().getHiddenColumns() == null))
2615 System.err.println("Bad things");
2617 if (repaint) // always true!
2619 // probably finessing with multiple redraws here
2620 PaintRefresher.Refresh(this, av.getSequenceSetId());
2621 // ap.paintAlignment(false);
2624 // lastly, update dependent dialogs
2625 if (ap.getCalculationDialog() != null)
2627 ap.getCalculationDialog().validateCalcTypes();
2633 * If this panel is a cdna/protein translation view of the selection source,
2634 * tries to map the source selection to a local one, and returns true. Else
2641 protected boolean selectionFromTranslation(SequenceGroup seqsel,
2642 ColumnSelection colsel, HiddenColumns hidden,
2643 SelectionSource source)
2645 if (!(source instanceof AlignViewportI))
2649 final AlignViewportI sourceAv = (AlignViewportI) source;
2650 if (sourceAv.getCodingComplement() != av
2651 && av.getCodingComplement() != sourceAv)
2657 * Map sequence selection
2659 SequenceGroup sg = MappingUtils.mapSequenceGroup(seqsel, sourceAv, av);
2660 av.setSelectionGroup(sg);
2661 av.isSelectionGroupChanged(true);
2664 * Map column selection
2666 // ColumnSelection cs = MappingUtils.mapColumnSelection(colsel, sourceAv,
2668 ColumnSelection cs = new ColumnSelection();
2669 HiddenColumns hs = new HiddenColumns();
2670 MappingUtils.mapColumnSelection(colsel, hidden, sourceAv, av, cs, hs);
2671 av.setColumnSelection(cs);
2672 boolean hiddenChanged = av.getAlignment().setHiddenColumns(hs);
2674 // lastly, update any dependent dialogs
2675 if (ap.getCalculationDialog() != null)
2677 ap.getCalculationDialog().validateCalcTypes();
2681 * repaint alignment, and also Overview or Structure
2682 * if hidden column selection has changed
2684 ap.paintAlignment(hiddenChanged, hiddenChanged);
2691 * @return null or last search results handled by this panel
2693 public SearchResultsI getLastSearchResults()
2695 return lastSearchResults;