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)
336 if (!av.getWrapAlignment())
338 return av.getAbsoluteColumn(x);
342 * wrapped mode calculation
344 final int startRes = av.getRanges().getStartRes();
345 final int charWidth = av.getCharWidth();
348 int hgap = av.getCharHeight();
349 if (av.getScaleAboveWrapped())
351 hgap += av.getCharHeight();
354 int cHeight = av.getAlignment().getHeight() * av.getCharHeight() + hgap
355 + seqCanvas.getAnnotationHeight();
358 y = Math.max(0, y - hgap);
359 x -= seqCanvas.getLabelWidthWest();
362 // mouse is over left scale
366 int cwidth = seqCanvas.getWrappedCanvasWidth(this.getWidth());
371 if (x >= cwidth * charWidth)
373 // mouse is over right scale
377 wrappedBlock = y / cHeight;
378 wrappedBlock += startRes / cwidth;
379 // allow for wrapped view scrolled right (possible from Overview)
380 int startOffset = startRes % cwidth;
381 res = wrappedBlock * cwidth + startOffset
382 + Math.min(cwidth - 1, x / charWidth);
384 if (av.hasHiddenColumns())
386 res = av.getAlignment().getHiddenColumns()
387 .visibleToAbsoluteColumn(res);
394 * When all of a sequence of edits are complete, put the resulting edit list
395 * on the history stack (undo list), and reset flags for editing in progress.
401 if (editCommand != null && editCommand.getSize() > 0)
403 ap.alignFrame.addHistoryItem(editCommand);
404 av.firePropertyChange("alignment", null,
405 av.getAlignment().getSequences());
410 * Tidy up come what may...
415 groupEditing = false;
424 seqCanvas.cursorY = getKeyboardNo1() - 1;
425 scrollToVisible(true);
428 void setCursorColumn()
430 seqCanvas.cursorX = getKeyboardNo1() - 1;
431 scrollToVisible(true);
434 void setCursorRowAndColumn()
436 if (keyboardNo2 == null)
438 keyboardNo2 = new StringBuffer();
442 seqCanvas.cursorX = getKeyboardNo1() - 1;
443 seqCanvas.cursorY = getKeyboardNo2() - 1;
444 scrollToVisible(true);
448 void setCursorPosition()
450 SequenceI sequence = av.getAlignment().getSequenceAt(seqCanvas.cursorY);
452 seqCanvas.cursorX = sequence.findIndex(getKeyboardNo1()) - 1;
453 scrollToVisible(true);
456 void moveCursor(int dx, int dy)
458 seqCanvas.cursorX += dx;
459 seqCanvas.cursorY += dy;
461 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
463 if (av.hasHiddenColumns() && !hidden.isVisible(seqCanvas.cursorX))
465 int original = seqCanvas.cursorX - dx;
466 int maxWidth = av.getAlignment().getWidth();
468 if (!hidden.isVisible(seqCanvas.cursorX))
470 int visx = hidden.absoluteToVisibleColumn(seqCanvas.cursorX - dx);
471 int[] region = hidden.getRegionWithEdgeAtRes(visx);
473 if (region != null) // just in case
478 seqCanvas.cursorX = region[1] + 1;
483 seqCanvas.cursorX = region[0] - 1;
486 seqCanvas.cursorX = (seqCanvas.cursorX < 0) ? 0 : seqCanvas.cursorX;
489 if (seqCanvas.cursorX >= maxWidth
490 || !hidden.isVisible(seqCanvas.cursorX))
492 seqCanvas.cursorX = original;
496 scrollToVisible(false);
500 * Scroll to make the cursor visible in the viewport.
503 * just jump to the location rather than scrolling
505 void scrollToVisible(boolean jump)
507 if (seqCanvas.cursorX < 0)
509 seqCanvas.cursorX = 0;
511 else if (seqCanvas.cursorX > av.getAlignment().getWidth() - 1)
513 seqCanvas.cursorX = av.getAlignment().getWidth() - 1;
516 if (seqCanvas.cursorY < 0)
518 seqCanvas.cursorY = 0;
520 else if (seqCanvas.cursorY > av.getAlignment().getHeight() - 1)
522 seqCanvas.cursorY = av.getAlignment().getHeight() - 1;
527 boolean repaintNeeded = true;
530 // only need to repaint if the viewport did not move, as otherwise it will
532 repaintNeeded = !av.getRanges().setViewportLocation(seqCanvas.cursorX,
537 if (av.getWrapAlignment())
539 // scrollToWrappedVisible expects x-value to have hidden cols subtracted
540 int x = av.getAlignment().getHiddenColumns()
541 .absoluteToVisibleColumn(seqCanvas.cursorX);
542 av.getRanges().scrollToWrappedVisible(x);
546 av.getRanges().scrollToVisible(seqCanvas.cursorX,
551 if (av.getAlignment().getHiddenColumns().isVisible(seqCanvas.cursorX))
553 setStatusMessage(av.getAlignment().getSequenceAt(seqCanvas.cursorY),
554 seqCanvas.cursorX, seqCanvas.cursorY);
564 void setSelectionAreaAtCursor(boolean topLeft)
566 SequenceI sequence = av.getAlignment().getSequenceAt(seqCanvas.cursorY);
568 if (av.getSelectionGroup() != null)
570 SequenceGroup sg = av.getSelectionGroup();
571 // Find the top and bottom of this group
572 int min = av.getAlignment().getHeight(), max = 0;
573 for (int i = 0; i < sg.getSize(); i++)
575 int index = av.getAlignment().findIndex(sg.getSequenceAt(i));
590 sg.setStartRes(seqCanvas.cursorX);
591 if (sg.getEndRes() < seqCanvas.cursorX)
593 sg.setEndRes(seqCanvas.cursorX);
596 min = seqCanvas.cursorY;
600 sg.setEndRes(seqCanvas.cursorX);
601 if (sg.getStartRes() > seqCanvas.cursorX)
603 sg.setStartRes(seqCanvas.cursorX);
606 max = seqCanvas.cursorY + 1;
611 // Only the user can do this
612 av.setSelectionGroup(null);
616 // Now add any sequences between min and max
617 sg.getSequences(null).clear();
618 for (int i = min; i < max; i++)
620 sg.addSequence(av.getAlignment().getSequenceAt(i), false);
625 if (av.getSelectionGroup() == null)
627 SequenceGroup sg = new SequenceGroup();
628 sg.setStartRes(seqCanvas.cursorX);
629 sg.setEndRes(seqCanvas.cursorX);
630 sg.addSequence(sequence, false);
631 av.setSelectionGroup(sg);
634 ap.paintAlignment(false, false);
638 void insertGapAtCursor(boolean group)
640 groupEditing = group;
641 editStartSeq = seqCanvas.cursorY;
642 editLastRes = seqCanvas.cursorX;
643 editSequence(true, false, seqCanvas.cursorX + getKeyboardNo1());
647 void deleteGapAtCursor(boolean group)
649 groupEditing = group;
650 editStartSeq = seqCanvas.cursorY;
651 editLastRes = seqCanvas.cursorX + getKeyboardNo1();
652 editSequence(false, false, seqCanvas.cursorX);
656 void insertNucAtCursor(boolean group, String nuc)
658 // TODO not called - delete?
659 groupEditing = group;
660 editStartSeq = seqCanvas.cursorY;
661 editLastRes = seqCanvas.cursorX;
662 editSequence(false, true, seqCanvas.cursorX + getKeyboardNo1());
666 void numberPressed(char value)
668 if (keyboardNo1 == null)
670 keyboardNo1 = new StringBuffer();
673 if (keyboardNo2 != null)
675 keyboardNo2.append(value);
679 keyboardNo1.append(value);
687 if (keyboardNo1 != null)
689 int value = Integer.parseInt(keyboardNo1.toString());
693 } catch (Exception x)
704 if (keyboardNo2 != null)
706 int value = Integer.parseInt(keyboardNo2.toString());
710 } catch (Exception x)
724 public void mouseReleased(MouseEvent evt)
726 MousePos pos = findMousePosition(evt);
727 if (pos.isOverAnnotation() || pos.seqIndex == -1 || pos.column == -1)
732 boolean didDrag = mouseDragging; // did we come here after a drag
733 mouseDragging = false;
734 mouseWheelPressed = false;
736 if (evt.isPopupTrigger()) // Windows: mouseReleased
738 showPopupMenu(evt, pos);
749 doMouseReleasedDefineMode(evt, didDrag);
760 public void mousePressed(MouseEvent evt)
762 lastMousePress = evt.getPoint();
763 MousePos pos = findMousePosition(evt);
764 if (pos.isOverAnnotation() || pos.seqIndex == -1 || pos.column == -1)
769 if (SwingUtilities.isMiddleMouseButton(evt))
771 mouseWheelPressed = true;
775 boolean isControlDown = Platform.isControlDown(evt);
776 if (evt.isShiftDown() || isControlDown)
786 doMousePressedDefineMode(evt, pos);
790 int seq = pos.seqIndex;
791 int res = pos.column;
793 if ((seq < av.getAlignment().getHeight())
794 && (res < av.getAlignment().getSequenceAt(seq).getLength()))
811 public void mouseOverSequence(SequenceI sequence, int index, int pos)
813 String tmp = sequence.hashCode() + " " + index + " " + pos;
815 if (lastMessage == null || !lastMessage.equals(tmp))
817 // System.err.println("mouseOver Sequence: "+tmp);
818 ssm.mouseOverSequence(sequence, index, pos, av);
824 * Highlight the mapped region described by the search results object (unless
825 * unchanged). This supports highlight of protein while mousing over linked
826 * cDNA and vice versa. The status bar is also updated to show the location of
827 * the start of the highlighted region.
830 public void highlightSequence(SearchResultsI results)
832 if (results == null || results.equals(lastSearchResults))
836 lastSearchResults = results;
838 boolean wasScrolled = false;
840 if (av.isFollowHighlight())
842 // don't allow highlight of protein/cDNA to also scroll a complementary
843 // panel,as this sets up a feedback loop (scrolling panel 1 causes moused
844 // over residue to change abruptly, causing highlighted residue in panel 2
845 // to change, causing a scroll in panel 1 etc)
846 ap.setToScrollComplementPanel(false);
847 wasScrolled = ap.scrollToPosition(results);
850 seqCanvas.revalidate();
852 ap.setToScrollComplementPanel(true);
855 boolean noFastPaint = wasScrolled && av.getWrapAlignment();
856 if (seqCanvas.highlightSearchResults(results, noFastPaint))
858 setStatusMessage(results);
863 public VamsasSource getVamsasSource()
865 return this.ap == null ? null : this.ap.av;
869 public void updateColours(SequenceI seq, int index)
871 System.out.println("update the seqPanel colours");
876 * Action on mouse movement is to update the status bar to show the current
877 * sequence position, and (if features are shown) to show any features at the
878 * position in a tooltip. Does nothing if the mouse move does not change
884 public void mouseMoved(MouseEvent evt)
888 // This is because MacOSX creates a mouseMoved
889 // If control is down, other platforms will not.
893 final MousePos mousePos = findMousePosition(evt);
894 if (mousePos.equals(lastMousePosition))
897 * just a pixel move without change of 'cell'
901 lastMousePosition = mousePos;
903 if (mousePos.isOverAnnotation())
905 mouseMovedOverAnnotation(mousePos);
908 final int seq = mousePos.seqIndex;
910 final int column = mousePos.column;
911 if (column < 0 || seq < 0 || seq >= av.getAlignment().getHeight())
913 lastMousePosition = null;
914 setToolTipText(null);
916 ap.alignFrame.setStatus("");
920 SequenceI sequence = av.getAlignment().getSequenceAt(seq);
922 if (column >= sequence.getLength())
928 * set status bar message, returning residue position in sequence
930 boolean isGapped = Comparison.isGap(sequence.getCharAt(column));
931 final int pos = setStatusMessage(sequence, column, seq);
932 if (ssm != null && !isGapped)
934 mouseOverSequence(sequence, column, pos);
937 tooltipText.setLength(6); // Cuts the buffer back to <html>
939 SequenceGroup[] groups = av.getAlignment().findAllGroups(sequence);
942 for (int g = 0; g < groups.length; g++)
944 if (groups[g].getStartRes() <= column
945 && groups[g].getEndRes() >= column)
947 if (!groups[g].getName().startsWith("JTreeGroup")
948 && !groups[g].getName().startsWith("JGroup"))
950 tooltipText.append(groups[g].getName());
953 if (groups[g].getDescription() != null)
955 tooltipText.append(": " + groups[g].getDescription());
962 * add any features at the position to the tooltip; if over a gap, only
963 * add features that straddle the gap (pos may be the residue before or
966 if (av.isShowSequenceFeatures())
968 List<SequenceFeature> features = ap.getFeatureRenderer()
969 .findFeaturesAtColumn(sequence, column + 1);
970 seqARep.appendFeatures(tooltipText, pos, features,
971 this.ap.getSeqPanel().seqCanvas.fr);
973 if (tooltipText.length() == 6) // <html>
975 setToolTipText(null);
980 if (tooltipText.length() > MAX_TOOLTIP_LENGTH) // constant
982 tooltipText.setLength(MAX_TOOLTIP_LENGTH);
983 tooltipText.append("...");
985 String textString = tooltipText.toString();
986 if (lastTooltip == null || !lastTooltip.equals(textString))
988 String formattedTooltipText = JvSwingUtils.wrapTooltip(true,
990 setToolTipText(formattedTooltipText);
991 lastTooltip = textString;
997 * When the view is in wrapped mode, and the mouse is over an annotation row,
998 * shows the corresponding tooltip and status message (if any)
1003 protected void mouseMovedOverAnnotation(MousePos pos)
1005 final int column = pos.column;
1006 final int rowIndex = pos.annotationIndex;
1008 if (column < 0 || !av.getWrapAlignment() || !av.isShowAnnotation()
1013 AlignmentAnnotation[] anns = av.getAlignment().getAlignmentAnnotation();
1015 String tooltip = AnnotationPanel.buildToolTip(anns[rowIndex], column,
1017 setToolTipText(tooltip);
1018 lastTooltip = tooltip;
1020 String msg = AnnotationPanel.getStatusMessage(av.getAlignment(), column,
1022 ap.alignFrame.setStatus(msg);
1025 private Point lastp = null;
1030 * @see javax.swing.JComponent#getToolTipLocation(java.awt.event.MouseEvent)
1033 public Point getToolTipLocation(MouseEvent event)
1035 if (tooltipText == null || tooltipText.length() <= 6)
1041 int x = event.getX();
1043 // switch sides when tooltip is too close to edge
1044 int wdth = (w - x < 200) ? -(w / 2) : 5;
1046 if (!event.isShiftDown() || p == null)
1048 p = new Point(event.getX() + wdth, event.getY() - 20);
1052 * TODO: try to set position so region is not obscured by tooltip
1060 * set when the current UI interaction has resulted in a change that requires
1061 * shading in overviews and structures to be recalculated. this could be
1062 * changed to a something more expressive that indicates what actually has
1063 * changed, so selective redraws can be applied (ie. only structures, only
1066 private boolean updateOverviewAndStructs = false; // TODO: refactor to avcontroller
1069 * set if av.getSelectionGroup() refers to a group that is defined on the
1070 * alignment view, rather than a transient selection
1072 // private boolean editingDefinedGroup = false; // TODO: refactor to
1073 // avcontroller or viewModel
1076 * Sets the status message in alignment panel, showing the sequence number
1077 * (index) and id, and residue and residue position if not at a gap, for the
1078 * given sequence and column position. Returns the residue position returned
1079 * by Sequence.findPosition. Note this may be for the nearest adjacent residue
1080 * if at a gapped position.
1083 * aligned sequence object
1087 * index of sequence in alignment
1088 * @return sequence position of residue at column, or adjacent residue if at a
1091 int setStatusMessage(SequenceI sequence, final int column, int seqIndex)
1093 char sequenceChar = sequence.getCharAt(column);
1094 int pos = sequence.findPosition(column);
1095 setStatusMessage(sequence, seqIndex, sequenceChar, pos);
1101 * Builds the status message for the current cursor location and writes it to
1102 * the status bar, for example
1105 * Sequence 3 ID: FER1_SOLLC
1106 * Sequence 5 ID: FER1_PEA Residue: THR (4)
1107 * Sequence 5 ID: FER1_PEA Residue: B (3)
1108 * Sequence 6 ID: O.niloticus.3 Nucleotide: Uracil (2)
1113 * sequence position in the alignment (1..)
1114 * @param sequenceChar
1115 * the character under the cursor
1117 * the sequence residue position (if not over a gap)
1119 protected void setStatusMessage(SequenceI sequence, int seqIndex,
1120 char sequenceChar, int residuePos)
1122 StringBuilder text = new StringBuilder(32);
1125 * Sequence number (if known), and sequence name.
1127 String seqno = seqIndex == -1 ? "" : " " + (seqIndex + 1);
1128 text.append("Sequence").append(seqno).append(" ID: ")
1129 .append(sequence.getName());
1131 String residue = null;
1134 * Try to translate the display character to residue name (null for gap).
1136 boolean isGapped = Comparison.isGap(sequenceChar);
1140 boolean nucleotide = av.getAlignment().isNucleotide();
1141 String displayChar = String.valueOf(sequenceChar);
1144 residue = ResidueProperties.nucleotideName.get(displayChar);
1148 residue = "X".equalsIgnoreCase(displayChar) ? "X"
1149 : ("*".equals(displayChar) ? "STOP"
1150 : ResidueProperties.aa2Triplet.get(displayChar));
1152 text.append(" ").append(nucleotide ? "Nucleotide" : "Residue")
1153 .append(": ").append(residue == null ? displayChar : residue);
1155 text.append(" (").append(Integer.toString(residuePos)).append(")");
1157 ap.alignFrame.setStatus(text.toString());
1161 * Set the status bar message to highlight the first matched position in
1166 private void setStatusMessage(SearchResultsI results)
1168 AlignmentI al = this.av.getAlignment();
1169 int sequenceIndex = al.findIndex(results);
1170 if (sequenceIndex == -1)
1174 SequenceI ds = al.getSequenceAt(sequenceIndex).getDatasetSequence();
1175 for (SearchResultMatchI m : results.getResults())
1177 SequenceI seq = m.getSequence();
1178 if (seq.getDatasetSequence() != null)
1180 seq = seq.getDatasetSequence();
1185 int start = m.getStart();
1186 setStatusMessage(seq, sequenceIndex, seq.getCharAt(start - 1),
1197 public void mouseDragged(MouseEvent evt)
1199 MousePos pos = findMousePosition(evt);
1200 if (pos.isOverAnnotation() || pos.column == -1)
1205 if (mouseWheelPressed)
1207 boolean inSplitFrame = ap.av.getCodingComplement() != null;
1208 boolean copyChanges = inSplitFrame && av.isProteinFontAsCdna();
1210 int oldWidth = av.getCharWidth();
1212 // Which is bigger, left-right or up-down?
1213 if (Math.abs(evt.getY() - lastMousePress.getY()) > Math
1214 .abs(evt.getX() - lastMousePress.getX()))
1217 * on drag up or down, decrement or increment font size
1219 int fontSize = av.font.getSize();
1220 boolean fontChanged = false;
1222 if (evt.getY() < lastMousePress.getY())
1227 else if (evt.getY() > lastMousePress.getY())
1240 Font newFont = new Font(av.font.getName(), av.font.getStyle(),
1242 av.setFont(newFont, true);
1243 av.setCharWidth(oldWidth);
1247 ap.av.getCodingComplement().setFont(newFont, true);
1248 SplitFrame splitFrame = (SplitFrame) ap.alignFrame
1249 .getSplitViewContainer();
1250 splitFrame.adjustLayout();
1251 splitFrame.repaint();
1258 * on drag left or right, decrement or increment character width
1261 if (evt.getX() < lastMousePress.getX() && av.getCharWidth() > 1)
1263 newWidth = av.getCharWidth() - 1;
1264 av.setCharWidth(newWidth);
1266 else if (evt.getX() > lastMousePress.getX())
1268 newWidth = av.getCharWidth() + 1;
1269 av.setCharWidth(newWidth);
1273 ap.paintAlignment(false, false);
1277 * need to ensure newWidth is set on cdna, regardless of which
1278 * panel the mouse drag happened in; protein will compute its
1279 * character width as 1:1 or 3:1
1281 av.getCodingComplement().setCharWidth(newWidth);
1282 SplitFrame splitFrame = (SplitFrame) ap.alignFrame
1283 .getSplitViewContainer();
1284 splitFrame.adjustLayout();
1285 splitFrame.repaint();
1290 FontMetrics fm = getFontMetrics(av.getFont());
1291 av.validCharWidth = fm.charWidth('M') <= av.getCharWidth();
1293 lastMousePress = evt.getPoint();
1300 dragStretchGroup(evt);
1304 int res = pos.column;
1311 if ((editLastRes == -1) || (editLastRes == res))
1316 if ((res < av.getAlignment().getWidth()) && (res < editLastRes))
1318 // dragLeft, delete gap
1319 editSequence(false, false, res);
1323 editSequence(true, false, res);
1326 mouseDragging = true;
1327 if (scrollThread != null)
1329 scrollThread.setMousePosition(evt.getPoint());
1334 * Edits the sequence to insert or delete one or more gaps, in response to a
1335 * mouse drag or cursor mode command. The number of inserts/deletes may be
1336 * specified with the cursor command, or else depends on the mouse event
1337 * (normally one column, but potentially more for a fast mouse drag).
1339 * Delete gaps is limited to the number of gaps left of the cursor position
1340 * (mouse drag), or at or right of the cursor position (cursor mode).
1342 * In group editing mode (Ctrl or Cmd down), the edit acts on all sequences in
1343 * the current selection group.
1345 * In locked editing mode (with a selection group present), inserts/deletions
1346 * within the selection group are limited to its boundaries (and edits outside
1347 * the group stop at its border).
1350 * true to insert gaps, false to delete gaps
1352 * (unused parameter)
1354 * the column at which to perform the action; the number of columns
1355 * affected depends on <code>this.editLastRes</code> (cursor column
1358 synchronized void editSequence(boolean insertGap, boolean editSeq,
1362 int fixedRight = -1;
1363 boolean fixedColumns = false;
1364 SequenceGroup sg = av.getSelectionGroup();
1366 final SequenceI seq = av.getAlignment().getSequenceAt(editStartSeq);
1368 // No group, but the sequence may represent a group
1369 if (!groupEditing && av.hasHiddenRows())
1371 if (av.isHiddenRepSequence(seq))
1373 sg = av.getRepresentedSequences(seq);
1374 groupEditing = true;
1378 StringBuilder message = new StringBuilder(64); // for status bar
1381 * make a name for the edit action, for
1382 * status bar message and Undo/Redo menu
1384 String label = null;
1387 message.append("Edit group:");
1388 label = MessageManager.getString("action.edit_group");
1392 message.append("Edit sequence: " + seq.getName());
1393 label = seq.getName();
1394 if (label.length() > 10)
1396 label = label.substring(0, 10);
1398 label = MessageManager.formatMessage("label.edit_params",
1404 * initialise the edit command if there is not
1405 * already one being extended
1407 if (editCommand == null)
1409 editCommand = new EditCommand(label);
1414 message.append(" insert ");
1418 message.append(" delete ");
1421 message.append(Math.abs(startres - editLastRes) + " gaps.");
1422 ap.alignFrame.setStatus(message.toString());
1425 * is there a selection group containing the sequence being edited?
1426 * if so the boundary of the group is the limit of the edit
1427 * (but the edit may be inside or outside the selection group)
1429 boolean inSelectionGroup = sg != null
1430 && sg.getSequences(av.getHiddenRepSequences()).contains(seq);
1431 if (groupEditing || inSelectionGroup)
1433 fixedColumns = true;
1435 // sg might be null as the user may only see 1 sequence,
1436 // but the sequence represents a group
1439 if (!av.isHiddenRepSequence(seq))
1444 sg = av.getRepresentedSequences(seq);
1447 fixedLeft = sg.getStartRes();
1448 fixedRight = sg.getEndRes();
1450 if ((startres < fixedLeft && editLastRes >= fixedLeft)
1451 || (startres >= fixedLeft && editLastRes < fixedLeft)
1452 || (startres > fixedRight && editLastRes <= fixedRight)
1453 || (startres <= fixedRight && editLastRes > fixedRight))
1459 if (fixedLeft > startres)
1461 fixedRight = fixedLeft - 1;
1464 else if (fixedRight < startres)
1466 fixedLeft = fixedRight;
1471 if (av.hasHiddenColumns())
1473 fixedColumns = true;
1474 int y1 = av.getAlignment().getHiddenColumns()
1475 .getNextHiddenBoundary(true, startres);
1476 int y2 = av.getAlignment().getHiddenColumns()
1477 .getNextHiddenBoundary(false, startres);
1479 if ((insertGap && startres > y1 && editLastRes < y1)
1480 || (!insertGap && startres < y2 && editLastRes > y2))
1486 // System.out.print(y1+" "+y2+" "+fixedLeft+" "+fixedRight+"~~");
1487 // Selection spans a hidden region
1488 if (fixedLeft < y1 && (fixedRight > y2 || fixedRight == -1))
1496 fixedRight = y2 - 1;
1501 boolean success = doEditSequence(insertGap, editSeq, startres,
1502 fixedRight, fixedColumns, sg);
1505 * report what actually happened (might be less than
1506 * what was requested), by inspecting the edit commands added
1508 String msg = getEditStatusMessage(editCommand);
1509 ap.alignFrame.setStatus(msg == null ? " " : msg);
1515 editLastRes = startres;
1516 seqCanvas.repaint();
1520 * A helper method that performs the requested editing to insert or delete
1521 * gaps (if possible). Answers true if the edit was successful, false if could
1522 * only be performed in part or not at all. Failure may occur in 'locked edit'
1523 * mode, when an insertion requires a matching gapped position (or column) to
1524 * delete, and deletion requires an adjacent gapped position (or column) to
1528 * true if inserting gap(s), false if deleting
1530 * (unused parameter, currently always false)
1532 * the column at which to perform the edit
1534 * fixed right boundary column of a locked edit (within or to the
1535 * left of a selection group)
1536 * @param fixedColumns
1537 * true if this is a locked edit
1539 * the sequence group (if group edit is being performed)
1542 protected boolean doEditSequence(final boolean insertGap,
1543 final boolean editSeq, final int startres, int fixedRight,
1544 final boolean fixedColumns, final SequenceGroup sg)
1546 final SequenceI seq = av.getAlignment().getSequenceAt(editStartSeq);
1547 SequenceI[] seqs = new SequenceI[] { seq };
1551 List<SequenceI> vseqs = sg.getSequences(av.getHiddenRepSequences());
1552 int g, groupSize = vseqs.size();
1553 SequenceI[] groupSeqs = new SequenceI[groupSize];
1554 for (g = 0; g < groupSeqs.length; g++)
1556 groupSeqs[g] = vseqs.get(g);
1562 // If the user has selected the whole sequence, and is dragging to
1563 // the right, we can still extend the alignment and selectionGroup
1564 if (sg.getStartRes() == 0 && sg.getEndRes() == fixedRight
1565 && sg.getEndRes() == av.getAlignment().getWidth() - 1)
1568 av.getAlignment().getWidth() + startres - editLastRes);
1569 fixedRight = sg.getEndRes();
1572 // Is it valid with fixed columns??
1573 // Find the next gap before the end
1574 // of the visible region boundary
1575 boolean blank = false;
1576 for (; fixedRight > editLastRes; fixedRight--)
1580 for (g = 0; g < groupSize; g++)
1582 for (int j = 0; j < startres - editLastRes; j++)
1585 .isGap(groupSeqs[g].getCharAt(fixedRight - j)))
1600 if (sg.getSize() == av.getAlignment().getHeight())
1602 if ((av.hasHiddenColumns()
1603 && startres < av.getAlignment().getHiddenColumns()
1604 .getNextHiddenBoundary(false, startres)))
1609 int alWidth = av.getAlignment().getWidth();
1610 if (av.hasHiddenRows())
1612 int hwidth = av.getAlignment().getHiddenSequences()
1614 if (hwidth > alWidth)
1619 // We can still insert gaps if the selectionGroup
1620 // contains all the sequences
1621 sg.setEndRes(sg.getEndRes() + startres - editLastRes);
1622 fixedRight = alWidth + startres - editLastRes;
1632 else if (!insertGap)
1634 // / Are we able to delete?
1635 // ie are all columns blank?
1637 for (g = 0; g < groupSize; g++)
1639 for (int j = startres; j < editLastRes; j++)
1641 if (groupSeqs[g].getLength() <= j)
1646 if (!Comparison.isGap(groupSeqs[g].getCharAt(j)))
1648 // Not a gap, block edit not valid
1657 // dragging to the right
1658 if (fixedColumns && fixedRight != -1)
1660 for (int j = editLastRes; j < startres; j++)
1662 insertGap(j, groupSeqs, fixedRight);
1667 appendEdit(Action.INSERT_GAP, groupSeqs, startres,
1668 startres - editLastRes, false);
1673 // dragging to the left
1674 if (fixedColumns && fixedRight != -1)
1676 for (int j = editLastRes; j > startres; j--)
1678 deleteChar(startres, groupSeqs, fixedRight);
1683 appendEdit(Action.DELETE_GAP, groupSeqs, startres,
1684 editLastRes - startres, false);
1691 * editing a single sequence
1695 // dragging to the right
1696 if (fixedColumns && fixedRight != -1)
1698 for (int j = editLastRes; j < startres; j++)
1700 if (!insertGap(j, seqs, fixedRight))
1703 * e.g. cursor mode command specified
1704 * more inserts than are possible
1712 appendEdit(Action.INSERT_GAP, seqs, editLastRes,
1713 startres - editLastRes, false);
1720 // dragging to the left
1721 if (fixedColumns && fixedRight != -1)
1723 for (int j = editLastRes; j > startres; j--)
1725 if (!Comparison.isGap(seq.getCharAt(startres)))
1729 deleteChar(startres, seqs, fixedRight);
1734 // could be a keyboard edit trying to delete none gaps
1736 for (int m = startres; m < editLastRes; m++)
1738 if (!Comparison.isGap(seq.getCharAt(m)))
1746 appendEdit(Action.DELETE_GAP, seqs, startres, max, false);
1751 {// insertGap==false AND editSeq==TRUE;
1752 if (fixedColumns && fixedRight != -1)
1754 for (int j = editLastRes; j < startres; j++)
1756 insertGap(j, seqs, fixedRight);
1761 appendEdit(Action.INSERT_NUC, seqs, editLastRes,
1762 startres - editLastRes, false);
1772 * Constructs an informative status bar message while dragging to insert or
1773 * delete gaps. Answers null if inserts and deletes cancel out.
1775 * @param editCommand
1776 * a command containing the list of individual edits
1779 protected static String getEditStatusMessage(EditCommand editCommand)
1781 if (editCommand == null)
1787 * add any inserts, and subtract any deletes,
1788 * not counting those auto-inserted when doing a 'locked edit'
1789 * (so only counting edits 'under the cursor')
1792 for (Edit cmd : editCommand.getEdits())
1794 if (!cmd.isSystemGenerated())
1796 count += cmd.getAction() == Action.INSERT_GAP ? cmd.getNumber()
1804 * inserts and deletes cancel out
1809 String msgKey = count > 1 ? "label.insert_gaps"
1810 : (count == 1 ? "label.insert_gap"
1811 : (count == -1 ? "label.delete_gap"
1812 : "label.delete_gaps"));
1813 count = Math.abs(count);
1815 return MessageManager.formatMessage(msgKey, String.valueOf(count));
1819 * Inserts one gap at column j, deleting the right-most gapped column up to
1820 * (and including) fixedColumn. Returns true if the edit is successful, false
1821 * if no blank column is available to allow the insertion to be balanced by a
1826 * @param fixedColumn
1829 boolean insertGap(int j, SequenceI[] seq, int fixedColumn)
1831 int blankColumn = fixedColumn;
1832 for (int s = 0; s < seq.length; s++)
1834 // Find the next gap before the end of the visible region boundary
1835 // If lastCol > j, theres a boundary after the gap insertion
1837 for (blankColumn = fixedColumn; blankColumn > j; blankColumn--)
1839 if (Comparison.isGap(seq[s].getCharAt(blankColumn)))
1841 // Theres a space, so break and insert the gap
1846 if (blankColumn <= j)
1848 blankColumn = fixedColumn;
1854 appendEdit(Action.DELETE_GAP, seq, blankColumn, 1, true);
1856 appendEdit(Action.INSERT_GAP, seq, j, 1, false);
1862 * Helper method to add and perform one edit action
1868 * @param systemGenerated
1869 * true if the edit is a 'balancing' delete (or insert) to match a
1870 * user's insert (or delete) in a locked editing region
1872 protected void appendEdit(Action action, SequenceI[] seq, int pos,
1873 int count, boolean systemGenerated)
1876 final Edit edit = new EditCommand().new Edit(action, seq, pos, count,
1877 av.getAlignment().getGapCharacter());
1878 edit.setSystemGenerated(systemGenerated);
1880 editCommand.appendEdit(edit, av.getAlignment(), true, null);
1884 * Deletes the character at column j, and inserts a gap at fixedColumn, in
1885 * each of the given sequences. The caller should ensure that all sequences
1886 * are gapped in column j.
1890 * @param fixedColumn
1892 void deleteChar(int j, SequenceI[] seqs, int fixedColumn)
1894 appendEdit(Action.DELETE_GAP, seqs, j, 1, false);
1896 appendEdit(Action.INSERT_GAP, seqs, fixedColumn, 1, true);
1900 * On reentering the panel, stops any scrolling that was started on dragging
1906 public void mouseEntered(MouseEvent e)
1916 * On leaving the panel, if the mouse is being dragged, starts a thread to
1917 * scroll it until the mouse is released (in unwrapped mode only)
1922 public void mouseExited(MouseEvent e)
1924 lastMousePosition = null;
1925 ap.alignFrame.setStatus(" ");
1926 if (av.getWrapAlignment())
1931 if (mouseDragging && scrollThread == null)
1933 scrollThread = new ScrollThread();
1938 * Handler for double-click on a position with one or more sequence features.
1939 * Opens the Amend Features dialog to allow feature details to be amended, or
1940 * the feature deleted.
1943 public void mouseClicked(MouseEvent evt)
1945 SequenceGroup sg = null;
1946 MousePos pos = findMousePosition(evt);
1947 if (pos.isOverAnnotation() || pos.seqIndex == -1 || pos.column == -1)
1952 if (evt.getClickCount() > 1)
1954 sg = av.getSelectionGroup();
1955 if (sg != null && sg.getSize() == 1
1956 && sg.getEndRes() - sg.getStartRes() < 2)
1958 av.setSelectionGroup(null);
1961 int column = pos.column;
1964 * find features at the position (if not gapped), or straddling
1965 * the position (if at a gap)
1967 SequenceI sequence = av.getAlignment().getSequenceAt(pos.seqIndex);
1968 List<SequenceFeature> features = seqCanvas.getFeatureRenderer()
1969 .findFeaturesAtColumn(sequence, column + 1);
1971 if (!features.isEmpty())
1974 * highlight the first feature at the position on the alignment
1976 SearchResultsI highlight = new SearchResults();
1977 highlight.addResult(sequence, features.get(0).getBegin(), features
1979 seqCanvas.highlightSearchResults(highlight, false);
1982 * open the Amend Features dialog; clear highlighting afterwards,
1983 * whether changes were made or not
1985 List<SequenceI> seqs = Collections.singletonList(sequence);
1986 seqCanvas.getFeatureRenderer().amendFeatures(seqs, features, false,
1988 av.setSearchResults(null); // clear highlighting
1989 seqCanvas.repaint(); // draw new/amended features
1995 public void mouseWheelMoved(MouseWheelEvent e)
1998 double wheelRotation = e.getPreciseWheelRotation();
1999 if (wheelRotation > 0)
2001 if (e.isShiftDown())
2003 av.getRanges().scrollRight(true);
2008 av.getRanges().scrollUp(false);
2011 else if (wheelRotation < 0)
2013 if (e.isShiftDown())
2015 av.getRanges().scrollRight(false);
2019 av.getRanges().scrollUp(true);
2024 * update status bar and tooltip for new position
2025 * (need to synthesize a mouse movement to refresh tooltip)
2028 ToolTipManager.sharedInstance().mouseMoved(e);
2037 protected void doMousePressedDefineMode(MouseEvent evt, MousePos pos)
2039 if (pos.isOverAnnotation() || pos.seqIndex == -1 || pos.column == -1)
2044 final int res = pos.column;
2045 final int seq = pos.seqIndex;
2047 updateOverviewAndStructs = false;
2049 startWrapBlock = wrappedBlock;
2051 SequenceI sequence = av.getAlignment().getSequenceAt(seq);
2053 if ((sequence == null) || (res > sequence.getLength()))
2058 stretchGroup = av.getSelectionGroup();
2060 if (stretchGroup == null || !stretchGroup.contains(sequence, res))
2062 stretchGroup = av.getAlignment().findGroup(sequence, res);
2063 if (stretchGroup != null)
2065 // only update the current selection if the popup menu has a group to
2067 av.setSelectionGroup(stretchGroup);
2071 if (evt.isPopupTrigger()) // Mac: mousePressed
2073 showPopupMenu(evt, pos);
2078 * defer right-mouse click handling to mouseReleased on Windows
2079 * (where isPopupTrigger() will answer true)
2080 * NB isRightMouseButton is also true for Cmd-click on Mac
2082 if (SwingUtilities.isRightMouseButton(evt) && !Platform.isAMac())
2089 seqCanvas.cursorX = res;
2090 seqCanvas.cursorY = seq;
2091 seqCanvas.repaint();
2095 if (stretchGroup == null)
2097 createStretchGroup(res, sequence);
2100 if (stretchGroup != null)
2102 stretchGroup.addPropertyChangeListener(seqCanvas);
2105 seqCanvas.repaint();
2108 private void createStretchGroup(int res, SequenceI sequence)
2110 // Only if left mouse button do we want to change group sizes
2111 // define a new group here
2112 SequenceGroup sg = new SequenceGroup();
2113 sg.setStartRes(res);
2115 sg.addSequence(sequence, false);
2116 av.setSelectionGroup(sg);
2119 if (av.getConservationSelected())
2121 SliderPanel.setConservationSlider(ap, av.getResidueShading(),
2125 if (av.getAbovePIDThreshold())
2127 SliderPanel.setPIDSliderSource(ap, av.getResidueShading(),
2130 // TODO: stretchGroup will always be not null. Is this a merge error ?
2131 // or is there a threading issue here?
2132 if ((stretchGroup != null) && (stretchGroup.getEndRes() == res))
2134 // Edit end res position of selected group
2135 changeEndRes = true;
2137 else if ((stretchGroup != null) && (stretchGroup.getStartRes() == res))
2139 // Edit end res position of selected group
2140 changeStartRes = true;
2142 stretchGroup.getWidth();
2147 * Build and show a pop-up menu at the right-click mouse position
2152 void showPopupMenu(MouseEvent evt, MousePos pos)
2154 final int column = pos.column;
2155 final int seq = pos.seqIndex;
2156 SequenceI sequence = av.getAlignment().getSequenceAt(seq);
2157 List<SequenceFeature> features = ap.getFeatureRenderer()
2158 .findFeaturesAtColumn(sequence, column + 1);
2160 PopupMenu pop = new PopupMenu(ap, null, features);
2161 pop.show(this, evt.getX(), evt.getY());
2165 * Update the display after mouse up on a selection or group
2168 * mouse released event details
2170 * true if this event is happening after a mouse drag (rather than a
2173 protected void doMouseReleasedDefineMode(MouseEvent evt,
2176 if (stretchGroup == null)
2181 stretchGroup.removePropertyChangeListener(seqCanvas);
2183 // always do this - annotation has own state
2184 // but defer colourscheme update until hidden sequences are passed in
2185 boolean vischange = stretchGroup.recalcConservation(true);
2186 updateOverviewAndStructs |= vischange && av.isSelectionDefinedGroup()
2188 if (stretchGroup.cs != null)
2192 stretchGroup.cs.alignmentChanged(stretchGroup,
2193 av.getHiddenRepSequences());
2196 ResidueShaderI groupColourScheme = stretchGroup
2197 .getGroupColourScheme();
2198 String name = stretchGroup.getName();
2199 if (stretchGroup.cs.conservationApplied())
2201 SliderPanel.setConservationSlider(ap, groupColourScheme, name);
2203 if (stretchGroup.cs.getThreshold() > 0)
2205 SliderPanel.setPIDSliderSource(ap, groupColourScheme, name);
2208 PaintRefresher.Refresh(this, av.getSequenceSetId());
2209 // TODO: structure colours only need updating if stretchGroup used to or now
2210 // does contain sequences with structure views
2211 ap.paintAlignment(updateOverviewAndStructs, updateOverviewAndStructs);
2212 updateOverviewAndStructs = false;
2213 changeEndRes = false;
2214 changeStartRes = false;
2215 stretchGroup = null;
2220 * Resizes the borders of a selection group depending on the direction of
2225 protected void dragStretchGroup(MouseEvent evt)
2227 if (stretchGroup == null)
2232 MousePos pos = findMousePosition(evt);
2233 if (pos.isOverAnnotation() || pos.column == -1 || pos.seqIndex == -1)
2238 int res = pos.column;
2239 int y = pos.seqIndex;
2241 if (wrappedBlock != startWrapBlock)
2246 res = Math.min(res, av.getAlignment().getWidth()-1);
2248 if (stretchGroup.getEndRes() == res)
2250 // Edit end res position of selected group
2251 changeEndRes = true;
2253 else if (stretchGroup.getStartRes() == res)
2255 // Edit start res position of selected group
2256 changeStartRes = true;
2259 if (res < av.getRanges().getStartRes())
2261 res = av.getRanges().getStartRes();
2266 if (res > (stretchGroup.getStartRes() - 1))
2268 stretchGroup.setEndRes(res);
2269 updateOverviewAndStructs |= av.isSelectionDefinedGroup();
2272 else if (changeStartRes)
2274 if (res < (stretchGroup.getEndRes() + 1))
2276 stretchGroup.setStartRes(res);
2277 updateOverviewAndStructs |= av.isSelectionDefinedGroup();
2281 int dragDirection = 0;
2287 else if (y < oldSeq)
2292 while ((y != oldSeq) && (oldSeq > -1)
2293 && (y < av.getAlignment().getHeight()))
2295 // This routine ensures we don't skip any sequences, as the
2296 // selection is quite slow.
2297 Sequence seq = (Sequence) av.getAlignment().getSequenceAt(oldSeq);
2299 oldSeq += dragDirection;
2306 Sequence nextSeq = (Sequence) av.getAlignment().getSequenceAt(oldSeq);
2308 if (stretchGroup.getSequences(null).contains(nextSeq))
2310 stretchGroup.deleteSequence(seq, false);
2311 updateOverviewAndStructs |= av.isSelectionDefinedGroup();
2317 stretchGroup.addSequence(seq, false);
2320 stretchGroup.addSequence(nextSeq, false);
2321 updateOverviewAndStructs |= av.isSelectionDefinedGroup();
2330 mouseDragging = true;
2332 if (scrollThread != null)
2334 scrollThread.setMousePosition(evt.getPoint());
2338 * construct a status message showing the range of the selection
2340 StringBuilder status = new StringBuilder(64);
2341 List<SequenceI> seqs = stretchGroup.getSequences();
2342 String name = seqs.get(0).getName();
2343 if (name.length() > 20)
2345 name = name.substring(0, 20);
2347 status.append(name).append(" - ");
2348 name = seqs.get(seqs.size() - 1).getName();
2349 if (name.length() > 20)
2351 name = name.substring(0, 20);
2353 status.append(name).append(" ");
2354 int startRes = stretchGroup.getStartRes();
2355 status.append(" cols ").append(String.valueOf(startRes + 1))
2357 int endRes = stretchGroup.getEndRes();
2358 status.append(String.valueOf(endRes + 1));
2359 status.append(" (").append(String.valueOf(seqs.size())).append(" x ")
2360 .append(String.valueOf(endRes - startRes + 1)).append(")");
2361 ap.alignFrame.setStatus(status.toString());
2365 * Stops the scroll thread if it is running
2367 void stopScrolling()
2369 if (scrollThread != null)
2371 scrollThread.stopScrolling();
2372 scrollThread = null;
2374 mouseDragging = false;
2378 * Starts a thread to scroll the alignment, towards a given mouse position
2379 * outside the panel bounds
2383 void startScrolling(Point mousePos)
2385 if (scrollThread == null)
2387 scrollThread = new ScrollThread();
2390 mouseDragging = true;
2391 scrollThread.setMousePosition(mousePos);
2395 * Performs scrolling of the visible alignment left, right, up or down
2397 class ScrollThread extends Thread
2399 private Point mousePos;
2401 private volatile boolean threadRunning = true;
2406 public ScrollThread()
2408 setName("SeqPanel$ScrollThread");
2413 * Sets the position of the mouse that determines the direction of the
2418 public void setMousePosition(Point p)
2424 * Sets a flag that will cause the thread to exit
2426 public void stopScrolling()
2428 threadRunning = false;
2432 * Scrolls the alignment left or right, and/or up or down, depending on the
2433 * last notified mouse position, until the limit of the alignment is
2434 * reached, or a flag is set to stop the scroll
2439 while (threadRunning && mouseDragging)
2441 if (mousePos != null)
2443 boolean scrolled = false;
2444 ViewportRanges ranges = SeqPanel.this.av.getRanges();
2451 // mouse is above this panel - try scroll up
2452 scrolled = ranges.scrollUp(true);
2454 else if (mousePos.y >= getHeight())
2456 // mouse is below this panel - try scroll down
2457 scrolled = ranges.scrollUp(false);
2461 * scroll left or right
2465 scrolled |= ranges.scrollRight(false);
2467 else if (mousePos.x >= getWidth())
2469 scrolled |= ranges.scrollRight(true);
2474 * we have reached the limit of the visible alignment - quit
2476 threadRunning = false;
2477 SeqPanel.this.ap.repaint();
2484 } catch (Exception ex)
2492 * modify current selection according to a received message.
2495 public void selection(SequenceGroup seqsel, ColumnSelection colsel,
2496 HiddenColumns hidden, SelectionSource source)
2498 // TODO: fix this hack - source of messages is align viewport, but SeqPanel
2499 // handles selection messages...
2500 // TODO: extend config options to allow user to control if selections may be
2501 // shared between viewports.
2502 boolean iSentTheSelection = (av == source
2503 || (source instanceof AlignViewport
2504 && ((AlignmentViewport) source).getSequenceSetId()
2505 .equals(av.getSequenceSetId())));
2507 if (iSentTheSelection)
2509 // respond to our own event by updating dependent dialogs
2510 if (ap.getCalculationDialog() != null)
2512 ap.getCalculationDialog().validateCalcTypes();
2518 // process further ?
2519 if (!av.followSelection)
2525 * Ignore the selection if there is one of our own pending.
2527 if (av.isSelectionGroupChanged(false) || av.isColSelChanged(false))
2533 * Check for selection in a view of which this one is a dna/protein
2536 if (selectionFromTranslation(seqsel, colsel, hidden, source))
2541 // do we want to thread this ? (contention with seqsel and colsel locks, I
2544 * only copy colsel if there is a real intersection between
2545 * sequence selection and this panel's alignment
2547 boolean repaint = false;
2548 boolean copycolsel = false;
2550 SequenceGroup sgroup = null;
2551 if (seqsel != null && seqsel.getSize() > 0)
2553 if (av.getAlignment() == null)
2555 Cache.log.warn("alignviewport av SeqSetId=" + av.getSequenceSetId()
2556 + " ViewId=" + av.getViewId()
2557 + " 's alignment is NULL! returning immediately.");
2560 sgroup = seqsel.intersect(av.getAlignment(),
2561 (av.hasHiddenRows()) ? av.getHiddenRepSequences() : null);
2562 if ((sgroup != null && sgroup.getSize() > 0))
2567 if (sgroup != null && sgroup.getSize() > 0)
2569 av.setSelectionGroup(sgroup);
2573 av.setSelectionGroup(null);
2575 av.isSelectionGroupChanged(true);
2580 // the current selection is unset or from a previous message
2581 // so import the new colsel.
2582 if (colsel == null || colsel.isEmpty())
2584 if (av.getColumnSelection() != null)
2586 av.getColumnSelection().clear();
2592 // TODO: shift colSel according to the intersecting sequences
2593 if (av.getColumnSelection() == null)
2595 av.setColumnSelection(new ColumnSelection(colsel));
2599 av.getColumnSelection().setElementsFrom(colsel,
2600 av.getAlignment().getHiddenColumns());
2603 av.isColSelChanged(true);
2607 if (copycolsel && av.hasHiddenColumns()
2608 && (av.getAlignment().getHiddenColumns() == null))
2610 System.err.println("Bad things");
2612 if (repaint) // always true!
2614 // probably finessing with multiple redraws here
2615 PaintRefresher.Refresh(this, av.getSequenceSetId());
2616 // ap.paintAlignment(false);
2619 // lastly, update dependent dialogs
2620 if (ap.getCalculationDialog() != null)
2622 ap.getCalculationDialog().validateCalcTypes();
2628 * If this panel is a cdna/protein translation view of the selection source,
2629 * tries to map the source selection to a local one, and returns true. Else
2636 protected boolean selectionFromTranslation(SequenceGroup seqsel,
2637 ColumnSelection colsel, HiddenColumns hidden,
2638 SelectionSource source)
2640 if (!(source instanceof AlignViewportI))
2644 final AlignViewportI sourceAv = (AlignViewportI) source;
2645 if (sourceAv.getCodingComplement() != av
2646 && av.getCodingComplement() != sourceAv)
2652 * Map sequence selection
2654 SequenceGroup sg = MappingUtils.mapSequenceGroup(seqsel, sourceAv, av);
2655 av.setSelectionGroup(sg);
2656 av.isSelectionGroupChanged(true);
2659 * Map column selection
2661 // ColumnSelection cs = MappingUtils.mapColumnSelection(colsel, sourceAv,
2663 ColumnSelection cs = new ColumnSelection();
2664 HiddenColumns hs = new HiddenColumns();
2665 MappingUtils.mapColumnSelection(colsel, hidden, sourceAv, av, cs, hs);
2666 av.setColumnSelection(cs);
2667 boolean hiddenChanged = av.getAlignment().setHiddenColumns(hs);
2669 // lastly, update any dependent dialogs
2670 if (ap.getCalculationDialog() != null)
2672 ap.getCalculationDialog().validateCalcTypes();
2676 * repaint alignment, and also Overview or Structure
2677 * if hidden column selection has changed
2679 ap.paintAlignment(hiddenChanged, hiddenChanged);
2686 * @return null or last search results handled by this panel
2688 public SearchResultsI getLastSearchResults()
2690 return lastSearchResults;