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;
53 import java.awt.BorderLayout;
54 import java.awt.Color;
56 import java.awt.FontMetrics;
57 import java.awt.Point;
58 import java.awt.event.MouseEvent;
59 import java.awt.event.MouseListener;
60 import java.awt.event.MouseMotionListener;
61 import java.awt.event.MouseWheelEvent;
62 import java.awt.event.MouseWheelListener;
63 import java.util.Collections;
64 import java.util.List;
66 import javax.swing.JPanel;
67 import javax.swing.SwingUtilities;
68 import javax.swing.ToolTipManager;
74 * @version $Revision: 1.130 $
76 public class SeqPanel extends JPanel
77 implements MouseListener, MouseMotionListener, MouseWheelListener,
78 SequenceListener, SelectionListener
81 * a class that holds computed mouse position
82 * - column of the alignment (0...)
83 * - sequence offset (0...)
84 * - annotation row offset (0...)
85 * where annotation offset is -1 unless the alignment is shown
86 * in wrapped mode, annotations are shown, and the mouse is
87 * over an annnotation row
92 * alignment column position of cursor (0...)
97 * index in alignment of sequence under cursor,
98 * or nearest above if cursor is not over a sequence
103 * index in annotations array of annotation under the cursor
104 * (only possible in wrapped mode with annotations shown),
105 * or -1 if cursor is not over an annotation row
107 final int annotationIndex;
109 MousePos(int col, int seq, int ann)
113 annotationIndex = ann;
116 boolean isOverAnnotation()
118 return annotationIndex != -1;
122 public boolean equals(Object obj)
124 if (obj == null || !(obj instanceof MousePos))
128 MousePos o = (MousePos) obj;
129 boolean b = (column == o.column && seqIndex == o.seqIndex
130 && annotationIndex == o.annotationIndex);
131 // System.out.println(obj + (b ? "= " : "!= ") + this);
136 * A simple hashCode that ensures that instances that satisfy equals() have
140 public int hashCode()
142 return column + seqIndex + annotationIndex;
146 * toString method for debug output purposes only
149 public String toString()
151 return String.format("c%d:s%d:a%d", column, seqIndex,
156 private static final int MAX_TOOLTIP_LENGTH = 300;
158 public SeqCanvas seqCanvas;
160 public AlignmentPanel ap;
163 * last position for mouseMoved event
165 private MousePos lastMousePosition;
167 protected int editLastRes;
169 protected int editStartSeq;
171 protected AlignViewport av;
173 ScrollThread scrollThread = null;
175 boolean mouseDragging = false;
177 boolean editingSeqs = false;
179 boolean groupEditing = false;
181 // ////////////////////////////////////////
182 // ///Everything below this is for defining the boundary of the rubberband
183 // ////////////////////////////////////////
186 boolean changeEndSeq = false;
188 boolean changeStartSeq = false;
190 boolean changeEndRes = false;
192 boolean changeStartRes = false;
194 SequenceGroup stretchGroup = null;
196 boolean remove = false;
198 Point lastMousePress;
200 boolean mouseWheelPressed = false;
202 StringBuffer keyboardNo1;
204 StringBuffer keyboardNo2;
206 java.net.URL linkImageURL;
208 private final SequenceAnnotationReport seqARep;
210 StringBuilder tooltipText = new StringBuilder();
214 EditCommand editCommand;
216 StructureSelectionManager ssm;
218 SearchResultsI lastSearchResults;
221 * Creates a new SeqPanel object
226 public SeqPanel(AlignViewport viewport, AlignmentPanel alignPanel)
228 linkImageURL = getClass().getResource("/images/link.gif");
229 seqARep = new SequenceAnnotationReport(linkImageURL.toString());
230 ToolTipManager.sharedInstance().registerComponent(this);
231 ToolTipManager.sharedInstance().setInitialDelay(0);
232 ToolTipManager.sharedInstance().setDismissDelay(10000);
234 setBackground(Color.white);
236 seqCanvas = new SeqCanvas(alignPanel);
237 setLayout(new BorderLayout());
238 add(seqCanvas, BorderLayout.CENTER);
240 this.ap = alignPanel;
242 if (!viewport.isDataset())
244 addMouseMotionListener(this);
245 addMouseListener(this);
246 addMouseWheelListener(this);
247 ssm = viewport.getStructureSelectionManager();
248 ssm.addStructureViewerListener(this);
249 ssm.addSelectionListener(this);
253 int startWrapBlock = -1;
255 int wrappedBlock = -1;
258 * Computes the column and sequence row (and possibly annotation row when in
259 * wrapped mode) for the given mouse position
264 MousePos findMousePosition(MouseEvent evt)
266 int col = findColumn(evt);
271 int charHeight = av.getCharHeight();
272 int alignmentHeight = av.getAlignment().getHeight();
273 if (av.getWrapAlignment())
275 seqCanvas.calculateWrappedGeometry(seqCanvas.getWidth(),
276 seqCanvas.getHeight());
279 * yPos modulo height of repeating width
281 int yOffsetPx = y % seqCanvas.wrappedRepeatHeightPx;
284 * height of sequences plus space / scale above,
285 * plus gap between sequences and annotations
287 int alignmentHeightPixels = seqCanvas.wrappedSpaceAboveAlignment
288 + alignmentHeight * charHeight
289 + SeqCanvas.SEQS_ANNOTATION_GAP;
290 if (yOffsetPx >= alignmentHeightPixels)
293 * mouse is over annotations; find annotation index, also set
294 * last sequence above (for backwards compatible behaviour)
296 AlignmentAnnotation[] anns = av.getAlignment()
297 .getAlignmentAnnotation();
298 int rowOffsetPx = yOffsetPx - alignmentHeightPixels;
299 annIndex = AnnotationPanel.getRowIndex(rowOffsetPx, anns);
300 seqIndex = alignmentHeight - 1;
305 * mouse is over sequence (or the space above sequences)
307 yOffsetPx -= seqCanvas.wrappedSpaceAboveAlignment;
310 seqIndex = Math.min(yOffsetPx / charHeight, alignmentHeight - 1);
316 seqIndex = Math.min((y / charHeight) + av.getRanges().getStartSeq(),
317 alignmentHeight - 1);
320 return new MousePos(col, seqIndex, annIndex);
323 * Returns the aligned sequence position (base 0) at the mouse position, or
324 * the closest visible one
329 int findColumn(MouseEvent evt)
334 final int startRes = av.getRanges().getStartRes();
335 final int charWidth = av.getCharWidth();
337 if (av.getWrapAlignment())
339 int hgap = av.getCharHeight();
340 if (av.getScaleAboveWrapped())
342 hgap += av.getCharHeight();
345 int cHeight = av.getAlignment().getHeight() * av.getCharHeight()
346 + hgap + seqCanvas.getAnnotationHeight();
349 y = Math.max(0, y - hgap);
350 x -= seqCanvas.getLabelWidthWest();
353 // mouse is over left scale
357 int cwidth = seqCanvas.getWrappedCanvasWidth(this.getWidth());
362 if (x >= cwidth * charWidth)
364 // mouse is over right scale
368 wrappedBlock = y / cHeight;
369 wrappedBlock += startRes / cwidth;
370 // allow for wrapped view scrolled right (possible from Overview)
371 int startOffset = startRes % cwidth;
372 res = wrappedBlock * cwidth + startOffset
373 + Math.min(cwidth - 1, x / charWidth);
378 * make sure we calculate relative to visible alignment,
379 * rather than right-hand gutter
381 x = Math.min(x, seqCanvas.getX() + seqCanvas.getWidth());
382 res = (x / charWidth) + startRes;
383 res = Math.min(res, av.getRanges().getEndRes());
386 if (av.hasHiddenColumns())
388 res = av.getAlignment().getHiddenColumns()
389 .visibleToAbsoluteColumn(res);
396 * When all of a sequence of edits are complete, put the resulting edit list
397 * on the history stack (undo list), and reset flags for editing in progress.
403 if (editCommand != null && editCommand.getSize() > 0)
405 ap.alignFrame.addHistoryItem(editCommand);
406 av.firePropertyChange("alignment", null,
407 av.getAlignment().getSequences());
412 * Tidy up come what may...
417 groupEditing = false;
426 seqCanvas.cursorY = getKeyboardNo1() - 1;
427 scrollToVisible(true);
430 void setCursorColumn()
432 seqCanvas.cursorX = getKeyboardNo1() - 1;
433 scrollToVisible(true);
436 void setCursorRowAndColumn()
438 if (keyboardNo2 == null)
440 keyboardNo2 = new StringBuffer();
444 seqCanvas.cursorX = getKeyboardNo1() - 1;
445 seqCanvas.cursorY = getKeyboardNo2() - 1;
446 scrollToVisible(true);
450 void setCursorPosition()
452 SequenceI sequence = av.getAlignment().getSequenceAt(seqCanvas.cursorY);
454 seqCanvas.cursorX = sequence.findIndex(getKeyboardNo1()) - 1;
455 scrollToVisible(true);
458 void moveCursor(int dx, int dy)
460 seqCanvas.cursorX += dx;
461 seqCanvas.cursorY += dy;
463 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
465 if (av.hasHiddenColumns() && !hidden.isVisible(seqCanvas.cursorX))
467 int original = seqCanvas.cursorX - dx;
468 int maxWidth = av.getAlignment().getWidth();
470 if (!hidden.isVisible(seqCanvas.cursorX))
472 int visx = hidden.absoluteToVisibleColumn(seqCanvas.cursorX - dx);
473 int[] region = hidden.getRegionWithEdgeAtRes(visx);
475 if (region != null) // just in case
480 seqCanvas.cursorX = region[1] + 1;
485 seqCanvas.cursorX = region[0] - 1;
488 seqCanvas.cursorX = (seqCanvas.cursorX < 0) ? 0 : seqCanvas.cursorX;
491 if (seqCanvas.cursorX >= maxWidth
492 || !hidden.isVisible(seqCanvas.cursorX))
494 seqCanvas.cursorX = original;
498 scrollToVisible(false);
502 * Scroll to make the cursor visible in the viewport.
505 * just jump to the location rather than scrolling
507 void scrollToVisible(boolean jump)
509 if (seqCanvas.cursorX < 0)
511 seqCanvas.cursorX = 0;
513 else if (seqCanvas.cursorX > av.getAlignment().getWidth() - 1)
515 seqCanvas.cursorX = av.getAlignment().getWidth() - 1;
518 if (seqCanvas.cursorY < 0)
520 seqCanvas.cursorY = 0;
522 else if (seqCanvas.cursorY > av.getAlignment().getHeight() - 1)
524 seqCanvas.cursorY = av.getAlignment().getHeight() - 1;
529 boolean repaintNeeded = true;
532 // only need to repaint if the viewport did not move, as otherwise it will
534 repaintNeeded = !av.getRanges().setViewportLocation(seqCanvas.cursorX,
539 if (av.getWrapAlignment())
541 // scrollToWrappedVisible expects x-value to have hidden cols subtracted
542 int x = av.getAlignment().getHiddenColumns()
543 .absoluteToVisibleColumn(seqCanvas.cursorX);
544 av.getRanges().scrollToWrappedVisible(x);
548 av.getRanges().scrollToVisible(seqCanvas.cursorX,
553 if (av.getAlignment().getHiddenColumns().isVisible(seqCanvas.cursorX))
555 setStatusMessage(av.getAlignment().getSequenceAt(seqCanvas.cursorY),
556 seqCanvas.cursorX, seqCanvas.cursorY);
566 void setSelectionAreaAtCursor(boolean topLeft)
568 SequenceI sequence = av.getAlignment().getSequenceAt(seqCanvas.cursorY);
570 if (av.getSelectionGroup() != null)
572 SequenceGroup sg = av.getSelectionGroup();
573 // Find the top and bottom of this group
574 int min = av.getAlignment().getHeight(), max = 0;
575 for (int i = 0; i < sg.getSize(); i++)
577 int index = av.getAlignment().findIndex(sg.getSequenceAt(i));
592 sg.setStartRes(seqCanvas.cursorX);
593 if (sg.getEndRes() < seqCanvas.cursorX)
595 sg.setEndRes(seqCanvas.cursorX);
598 min = seqCanvas.cursorY;
602 sg.setEndRes(seqCanvas.cursorX);
603 if (sg.getStartRes() > seqCanvas.cursorX)
605 sg.setStartRes(seqCanvas.cursorX);
608 max = seqCanvas.cursorY + 1;
613 // Only the user can do this
614 av.setSelectionGroup(null);
618 // Now add any sequences between min and max
619 sg.getSequences(null).clear();
620 for (int i = min; i < max; i++)
622 sg.addSequence(av.getAlignment().getSequenceAt(i), false);
627 if (av.getSelectionGroup() == null)
629 SequenceGroup sg = new SequenceGroup();
630 sg.setStartRes(seqCanvas.cursorX);
631 sg.setEndRes(seqCanvas.cursorX);
632 sg.addSequence(sequence, false);
633 av.setSelectionGroup(sg);
636 ap.paintAlignment(false, false);
640 void insertGapAtCursor(boolean group)
642 groupEditing = group;
643 editStartSeq = seqCanvas.cursorY;
644 editLastRes = seqCanvas.cursorX;
645 editSequence(true, false, seqCanvas.cursorX + getKeyboardNo1());
649 void deleteGapAtCursor(boolean group)
651 groupEditing = group;
652 editStartSeq = seqCanvas.cursorY;
653 editLastRes = seqCanvas.cursorX + getKeyboardNo1();
654 editSequence(false, false, seqCanvas.cursorX);
658 void insertNucAtCursor(boolean group, String nuc)
660 // TODO not called - delete?
661 groupEditing = group;
662 editStartSeq = seqCanvas.cursorY;
663 editLastRes = seqCanvas.cursorX;
664 editSequence(false, true, seqCanvas.cursorX + getKeyboardNo1());
668 void numberPressed(char value)
670 if (keyboardNo1 == null)
672 keyboardNo1 = new StringBuffer();
675 if (keyboardNo2 != null)
677 keyboardNo2.append(value);
681 keyboardNo1.append(value);
689 if (keyboardNo1 != null)
691 int value = Integer.parseInt(keyboardNo1.toString());
695 } catch (Exception x)
706 if (keyboardNo2 != null)
708 int value = Integer.parseInt(keyboardNo2.toString());
712 } catch (Exception x)
726 public void mouseReleased(MouseEvent evt)
728 MousePos pos = findMousePosition(evt);
729 if (pos.isOverAnnotation() || pos.seqIndex == -1 || pos.column == -1)
734 boolean didDrag = mouseDragging; // did we come here after a drag
735 mouseDragging = false;
736 mouseWheelPressed = false;
738 if (evt.isPopupTrigger()) // Windows: mouseReleased
740 showPopupMenu(evt, pos);
747 doMouseReleasedDefineMode(evt, didDrag);
761 public void mousePressed(MouseEvent evt)
763 lastMousePress = evt.getPoint();
764 MousePos pos = findMousePosition(evt);
765 if (pos.isOverAnnotation() || pos.seqIndex == -1 || pos.column == -1)
770 if (SwingUtilities.isMiddleMouseButton(evt))
772 mouseWheelPressed = true;
776 boolean isControlDown = Platform.isControlDown(evt);
777 if (evt.isShiftDown() || isControlDown)
787 doMousePressedDefineMode(evt, pos);
791 int seq = pos.seqIndex;
792 int res = pos.column;
794 if ((seq < av.getAlignment().getHeight())
795 && (res < av.getAlignment().getSequenceAt(seq).getLength()))
812 public void mouseOverSequence(SequenceI sequence, int index, int pos)
814 String tmp = sequence.hashCode() + " " + index + " " + pos;
816 if (lastMessage == null || !lastMessage.equals(tmp))
818 // System.err.println("mouseOver Sequence: "+tmp);
819 ssm.mouseOverSequence(sequence, index, pos, av);
825 * Highlight the mapped region described by the search results object (unless
826 * unchanged). This supports highlight of protein while mousing over linked
827 * cDNA and vice versa. The status bar is also updated to show the location of
828 * the start of the highlighted region.
831 public void highlightSequence(SearchResultsI results)
833 if (results == null || results.equals(lastSearchResults))
837 lastSearchResults = results;
839 boolean wasScrolled = false;
841 if (av.isFollowHighlight())
843 // don't allow highlight of protein/cDNA to also scroll a complementary
844 // panel,as this sets up a feedback loop (scrolling panel 1 causes moused
845 // over residue to change abruptly, causing highlighted residue in panel 2
846 // to change, causing a scroll in panel 1 etc)
847 ap.setToScrollComplementPanel(false);
848 wasScrolled = ap.scrollToPosition(results);
851 seqCanvas.revalidate();
853 ap.setToScrollComplementPanel(true);
856 boolean noFastPaint = wasScrolled && av.getWrapAlignment();
857 if (seqCanvas.highlightSearchResults(results, noFastPaint))
859 setStatusMessage(results);
864 public VamsasSource getVamsasSource()
866 return this.ap == null ? null : this.ap.av;
870 public void updateColours(SequenceI seq, int index)
872 System.out.println("update the seqPanel colours");
877 * Action on mouse movement is to update the status bar to show the current
878 * sequence position, and (if features are shown) to show any features at the
879 * position in a tooltip. Does nothing if the mouse move does not change
885 public void mouseMoved(MouseEvent evt)
889 // This is because MacOSX creates a mouseMoved
890 // If control is down, other platforms will not.
894 final MousePos mousePos = findMousePosition(evt);
895 if (mousePos.equals(lastMousePosition))
898 * just a pixel move without change of 'cell'
902 lastMousePosition = mousePos;
904 if (mousePos.isOverAnnotation())
906 mouseMovedOverAnnotation(mousePos);
909 final int seq = mousePos.seqIndex;
911 final int column = mousePos.column;
912 if (column < 0 || seq < 0 || seq >= av.getAlignment().getHeight())
914 lastMousePosition = null;
915 setToolTipText(null);
917 ap.alignFrame.setStatus("");
921 SequenceI sequence = av.getAlignment().getSequenceAt(seq);
923 if (column >= sequence.getLength())
929 * set status bar message, returning residue position in sequence
931 boolean isGapped = Comparison.isGap(sequence.getCharAt(column));
932 final int pos = setStatusMessage(sequence, column, seq);
933 if (ssm != null && !isGapped)
935 mouseOverSequence(sequence, column, pos);
938 tooltipText.setLength(6); // Cuts the buffer back to <html>
940 SequenceGroup[] groups = av.getAlignment().findAllGroups(sequence);
943 for (int g = 0; g < groups.length; g++)
945 if (groups[g].getStartRes() <= column
946 && groups[g].getEndRes() >= column)
948 if (!groups[g].getName().startsWith("JTreeGroup")
949 && !groups[g].getName().startsWith("JGroup"))
951 tooltipText.append(groups[g].getName());
954 if (groups[g].getDescription() != null)
956 tooltipText.append(": " + groups[g].getDescription());
963 * add any features at the position to the tooltip; if over a gap, only
964 * add features that straddle the gap (pos may be the residue before or
967 if (av.isShowSequenceFeatures())
969 List<SequenceFeature> features = ap.getFeatureRenderer()
970 .findFeaturesAtColumn(sequence, column + 1);
971 seqARep.appendFeatures(tooltipText, pos, features,
972 this.ap.getSeqPanel().seqCanvas.fr);
974 if (tooltipText.length() == 6) // <html>
976 setToolTipText(null);
981 if (tooltipText.length() > MAX_TOOLTIP_LENGTH) // constant
983 tooltipText.setLength(MAX_TOOLTIP_LENGTH);
984 tooltipText.append("...");
986 String textString = tooltipText.toString();
987 if (lastTooltip == null || !lastTooltip.equals(textString))
989 String formattedTooltipText = JvSwingUtils.wrapTooltip(true,
991 setToolTipText(formattedTooltipText);
992 lastTooltip = textString;
998 * When the view is in wrapped mode, and the mouse is over an annotation row,
999 * shows the corresponding tooltip and status message (if any)
1004 protected void mouseMovedOverAnnotation(MousePos pos)
1006 final int column = pos.column;
1007 final int rowIndex = pos.annotationIndex;
1009 if (column < 0 || !av.getWrapAlignment() || !av.isShowAnnotation()
1014 AlignmentAnnotation[] anns = av.getAlignment().getAlignmentAnnotation();
1016 String tooltip = AnnotationPanel.buildToolTip(anns[rowIndex], column,
1018 setToolTipText(tooltip);
1019 lastTooltip = tooltip;
1021 String msg = AnnotationPanel.getStatusMessage(av.getAlignment(), column,
1023 ap.alignFrame.setStatus(msg);
1026 private Point lastp = null;
1031 * @see javax.swing.JComponent#getToolTipLocation(java.awt.event.MouseEvent)
1034 public Point getToolTipLocation(MouseEvent event)
1036 if (tooltipText == null || tooltipText.length() <= 6)
1042 int x = event.getX();
1044 // switch sides when tooltip is too close to edge
1045 int wdth = (w - x < 200) ? -(w / 2) : 5;
1047 if (!event.isShiftDown() || p == null)
1049 p = new Point(event.getX() + wdth, event.getY() - 20);
1053 * TODO: try to set position so region is not obscured by tooltip
1061 * set when the current UI interaction has resulted in a change that requires
1062 * shading in overviews and structures to be recalculated. this could be
1063 * changed to a something more expressive that indicates what actually has
1064 * changed, so selective redraws can be applied (ie. only structures, only
1067 private boolean updateOverviewAndStructs = false; // TODO: refactor to avcontroller
1070 * set if av.getSelectionGroup() refers to a group that is defined on the
1071 * alignment view, rather than a transient selection
1073 // private boolean editingDefinedGroup = false; // TODO: refactor to
1074 // avcontroller or viewModel
1077 * Sets the status message in alignment panel, showing the sequence number
1078 * (index) and id, and residue and residue position if not at a gap, for the
1079 * given sequence and column position. Returns the residue position returned
1080 * by Sequence.findPosition. Note this may be for the nearest adjacent residue
1081 * if at a gapped position.
1084 * aligned sequence object
1088 * index of sequence in alignment
1089 * @return sequence position of residue at column, or adjacent residue if at a
1092 int setStatusMessage(SequenceI sequence, final int column, int seqIndex)
1094 char sequenceChar = sequence.getCharAt(column);
1095 int pos = sequence.findPosition(column);
1096 setStatusMessage(sequence, seqIndex, sequenceChar, pos);
1102 * Builds the status message for the current cursor location and writes it to
1103 * the status bar, for example
1106 * Sequence 3 ID: FER1_SOLLC
1107 * Sequence 5 ID: FER1_PEA Residue: THR (4)
1108 * Sequence 5 ID: FER1_PEA Residue: B (3)
1109 * Sequence 6 ID: O.niloticus.3 Nucleotide: Uracil (2)
1114 * sequence position in the alignment (1..)
1115 * @param sequenceChar
1116 * the character under the cursor
1118 * the sequence residue position (if not over a gap)
1120 protected void setStatusMessage(SequenceI sequence, int seqIndex,
1121 char sequenceChar, int residuePos)
1123 StringBuilder text = new StringBuilder(32);
1126 * Sequence number (if known), and sequence name.
1128 String seqno = seqIndex == -1 ? "" : " " + (seqIndex + 1);
1129 text.append("Sequence").append(seqno).append(" ID: ")
1130 .append(sequence.getName());
1132 String residue = null;
1135 * Try to translate the display character to residue name (null for gap).
1137 boolean isGapped = Comparison.isGap(sequenceChar);
1141 boolean nucleotide = av.getAlignment().isNucleotide();
1142 String displayChar = String.valueOf(sequenceChar);
1145 residue = ResidueProperties.nucleotideName.get(displayChar);
1149 residue = "X".equalsIgnoreCase(displayChar) ? "X"
1150 : ("*".equals(displayChar) ? "STOP"
1151 : ResidueProperties.aa2Triplet.get(displayChar));
1153 text.append(" ").append(nucleotide ? "Nucleotide" : "Residue")
1154 .append(": ").append(residue == null ? displayChar : residue);
1156 text.append(" (").append(Integer.toString(residuePos)).append(")");
1158 ap.alignFrame.setStatus(text.toString());
1162 * Set the status bar message to highlight the first matched position in
1167 private void setStatusMessage(SearchResultsI results)
1169 AlignmentI al = this.av.getAlignment();
1170 int sequenceIndex = al.findIndex(results);
1171 if (sequenceIndex == -1)
1175 SequenceI ds = al.getSequenceAt(sequenceIndex).getDatasetSequence();
1176 for (SearchResultMatchI m : results.getResults())
1178 SequenceI seq = m.getSequence();
1179 if (seq.getDatasetSequence() != null)
1181 seq = seq.getDatasetSequence();
1186 int start = m.getStart();
1187 setStatusMessage(seq, sequenceIndex, seq.getCharAt(start - 1),
1198 public void mouseDragged(MouseEvent evt)
1200 MousePos pos = findMousePosition(evt);
1201 if (pos.isOverAnnotation() || pos.column == -1)
1206 if (mouseWheelPressed)
1208 boolean inSplitFrame = ap.av.getCodingComplement() != null;
1209 boolean copyChanges = inSplitFrame && av.isProteinFontAsCdna();
1211 int oldWidth = av.getCharWidth();
1213 // Which is bigger, left-right or up-down?
1214 if (Math.abs(evt.getY() - lastMousePress.getY()) > Math
1215 .abs(evt.getX() - lastMousePress.getX()))
1218 * on drag up or down, decrement or increment font size
1220 int fontSize = av.font.getSize();
1221 boolean fontChanged = false;
1223 if (evt.getY() < lastMousePress.getY())
1228 else if (evt.getY() > lastMousePress.getY())
1241 Font newFont = new Font(av.font.getName(), av.font.getStyle(),
1243 av.setFont(newFont, true);
1244 av.setCharWidth(oldWidth);
1248 ap.av.getCodingComplement().setFont(newFont, true);
1249 SplitFrame splitFrame = (SplitFrame) ap.alignFrame
1250 .getSplitViewContainer();
1251 splitFrame.adjustLayout();
1252 splitFrame.repaint();
1259 * on drag left or right, decrement or increment character width
1262 if (evt.getX() < lastMousePress.getX() && av.getCharWidth() > 1)
1264 newWidth = av.getCharWidth() - 1;
1265 av.setCharWidth(newWidth);
1267 else if (evt.getX() > lastMousePress.getX())
1269 newWidth = av.getCharWidth() + 1;
1270 av.setCharWidth(newWidth);
1274 ap.paintAlignment(false, false);
1278 * need to ensure newWidth is set on cdna, regardless of which
1279 * panel the mouse drag happened in; protein will compute its
1280 * character width as 1:1 or 3:1
1282 av.getCodingComplement().setCharWidth(newWidth);
1283 SplitFrame splitFrame = (SplitFrame) ap.alignFrame
1284 .getSplitViewContainer();
1285 splitFrame.adjustLayout();
1286 splitFrame.repaint();
1291 FontMetrics fm = getFontMetrics(av.getFont());
1292 av.validCharWidth = fm.charWidth('M') <= av.getCharWidth();
1294 lastMousePress = evt.getPoint();
1301 dragStretchGroup(evt);
1305 int res = pos.column;
1312 if ((editLastRes == -1) || (editLastRes == res))
1317 if ((res < av.getAlignment().getWidth()) && (res < editLastRes))
1319 // dragLeft, delete gap
1320 editSequence(false, false, res);
1324 editSequence(true, false, res);
1327 mouseDragging = true;
1328 if ((scrollThread != null) && (scrollThread.isRunning()))
1330 scrollThread.setEvent(evt);
1335 * Edits the sequence to insert or delete one or more gaps, in response to a
1336 * mouse drag or cursor mode command. The number of inserts/deletes may be
1337 * specified with the cursor command, or else depends on the mouse event
1338 * (normally one column, but potentially more for a fast mouse drag).
1340 * Delete gaps is limited to the number of gaps left of the cursor position
1341 * (mouse drag), or at or right of the cursor position (cursor mode).
1343 * In group editing mode (Ctrl or Cmd down), the edit acts on all sequences in
1344 * the current selection group.
1346 * In locked editing mode (with a selection group present), inserts/deletions
1347 * within the selection group are limited to its boundaries (and edits outside
1348 * the group stop at its border).
1351 * true to insert gaps, false to delete gaps
1353 * (unused parameter)
1355 * the column at which to perform the action; the number of columns
1356 * affected depends on <code>this.editLastRes</code> (cursor column
1359 synchronized void editSequence(boolean insertGap, boolean editSeq,
1363 int fixedRight = -1;
1364 boolean fixedColumns = false;
1365 SequenceGroup sg = av.getSelectionGroup();
1367 final SequenceI seq = av.getAlignment().getSequenceAt(editStartSeq);
1369 // No group, but the sequence may represent a group
1370 if (!groupEditing && av.hasHiddenRows())
1372 if (av.isHiddenRepSequence(seq))
1374 sg = av.getRepresentedSequences(seq);
1375 groupEditing = true;
1379 StringBuilder message = new StringBuilder(64); // for status bar
1382 * make a name for the edit action, for
1383 * status bar message and Undo/Redo menu
1385 String label = null;
1388 message.append("Edit group:");
1389 label = MessageManager.getString("action.edit_group");
1393 message.append("Edit sequence: " + seq.getName());
1394 label = seq.getName();
1395 if (label.length() > 10)
1397 label = label.substring(0, 10);
1399 label = MessageManager.formatMessage("label.edit_params",
1405 * initialise the edit command if there is not
1406 * already one being extended
1408 if (editCommand == null)
1410 editCommand = new EditCommand(label);
1415 message.append(" insert ");
1419 message.append(" delete ");
1422 message.append(Math.abs(startres - editLastRes) + " gaps.");
1423 ap.alignFrame.setStatus(message.toString());
1426 * is there a selection group containing the sequence being edited?
1427 * if so the boundary of the group is the limit of the edit
1428 * (but the edit may be inside or outside the selection group)
1430 boolean inSelectionGroup = sg != null
1431 && sg.getSequences(av.getHiddenRepSequences()).contains(seq);
1432 if (groupEditing || inSelectionGroup)
1434 fixedColumns = true;
1436 // sg might be null as the user may only see 1 sequence,
1437 // but the sequence represents a group
1440 if (!av.isHiddenRepSequence(seq))
1445 sg = av.getRepresentedSequences(seq);
1448 fixedLeft = sg.getStartRes();
1449 fixedRight = sg.getEndRes();
1451 if ((startres < fixedLeft && editLastRes >= fixedLeft)
1452 || (startres >= fixedLeft && editLastRes < fixedLeft)
1453 || (startres > fixedRight && editLastRes <= fixedRight)
1454 || (startres <= fixedRight && editLastRes > fixedRight))
1460 if (fixedLeft > startres)
1462 fixedRight = fixedLeft - 1;
1465 else if (fixedRight < startres)
1467 fixedLeft = fixedRight;
1472 if (av.hasHiddenColumns())
1474 fixedColumns = true;
1475 int y1 = av.getAlignment().getHiddenColumns()
1476 .getNextHiddenBoundary(true, startres);
1477 int y2 = av.getAlignment().getHiddenColumns()
1478 .getNextHiddenBoundary(false, startres);
1480 if ((insertGap && startres > y1 && editLastRes < y1)
1481 || (!insertGap && startres < y2 && editLastRes > y2))
1487 // System.out.print(y1+" "+y2+" "+fixedLeft+" "+fixedRight+"~~");
1488 // Selection spans a hidden region
1489 if (fixedLeft < y1 && (fixedRight > y2 || fixedRight == -1))
1497 fixedRight = y2 - 1;
1502 boolean success = doEditSequence(insertGap, editSeq, startres,
1503 fixedRight, fixedColumns, sg);
1506 * report what actually happened (might be less than
1507 * what was requested), by inspecting the edit commands added
1509 String msg = getEditStatusMessage(editCommand);
1510 ap.alignFrame.setStatus(msg == null ? " " : msg);
1516 editLastRes = startres;
1517 seqCanvas.repaint();
1521 * A helper method that performs the requested editing to insert or delete
1522 * gaps (if possible). Answers true if the edit was successful, false if could
1523 * only be performed in part or not at all. Failure may occur in 'locked edit'
1524 * mode, when an insertion requires a matching gapped position (or column) to
1525 * delete, and deletion requires an adjacent gapped position (or column) to
1529 * true if inserting gap(s), false if deleting
1531 * (unused parameter, currently always false)
1533 * the column at which to perform the edit
1535 * fixed right boundary column of a locked edit (within or to the
1536 * left of a selection group)
1537 * @param fixedColumns
1538 * true if this is a locked edit
1540 * the sequence group (if group edit is being performed)
1543 protected boolean doEditSequence(final boolean insertGap,
1544 final boolean editSeq, final int startres, int fixedRight,
1545 final boolean fixedColumns, final SequenceGroup sg)
1547 final SequenceI seq = av.getAlignment().getSequenceAt(editStartSeq);
1548 SequenceI[] seqs = new SequenceI[] { seq };
1552 List<SequenceI> vseqs = sg.getSequences(av.getHiddenRepSequences());
1553 int g, groupSize = vseqs.size();
1554 SequenceI[] groupSeqs = new SequenceI[groupSize];
1555 for (g = 0; g < groupSeqs.length; g++)
1557 groupSeqs[g] = vseqs.get(g);
1563 // If the user has selected the whole sequence, and is dragging to
1564 // the right, we can still extend the alignment and selectionGroup
1565 if (sg.getStartRes() == 0 && sg.getEndRes() == fixedRight
1566 && sg.getEndRes() == av.getAlignment().getWidth() - 1)
1569 av.getAlignment().getWidth() + startres - editLastRes);
1570 fixedRight = sg.getEndRes();
1573 // Is it valid with fixed columns??
1574 // Find the next gap before the end
1575 // of the visible region boundary
1576 boolean blank = false;
1577 for (; fixedRight > editLastRes; fixedRight--)
1581 for (g = 0; g < groupSize; g++)
1583 for (int j = 0; j < startres - editLastRes; j++)
1586 .isGap(groupSeqs[g].getCharAt(fixedRight - j)))
1601 if (sg.getSize() == av.getAlignment().getHeight())
1603 if ((av.hasHiddenColumns()
1604 && startres < av.getAlignment().getHiddenColumns()
1605 .getNextHiddenBoundary(false, startres)))
1610 int alWidth = av.getAlignment().getWidth();
1611 if (av.hasHiddenRows())
1613 int hwidth = av.getAlignment().getHiddenSequences()
1615 if (hwidth > alWidth)
1620 // We can still insert gaps if the selectionGroup
1621 // contains all the sequences
1622 sg.setEndRes(sg.getEndRes() + startres - editLastRes);
1623 fixedRight = alWidth + startres - editLastRes;
1633 else if (!insertGap)
1635 // / Are we able to delete?
1636 // ie are all columns blank?
1638 for (g = 0; g < groupSize; g++)
1640 for (int j = startres; j < editLastRes; j++)
1642 if (groupSeqs[g].getLength() <= j)
1647 if (!Comparison.isGap(groupSeqs[g].getCharAt(j)))
1649 // Not a gap, block edit not valid
1658 // dragging to the right
1659 if (fixedColumns && fixedRight != -1)
1661 for (int j = editLastRes; j < startres; j++)
1663 insertGap(j, groupSeqs, fixedRight);
1668 appendEdit(Action.INSERT_GAP, groupSeqs, startres,
1669 startres - editLastRes, false);
1674 // dragging to the left
1675 if (fixedColumns && fixedRight != -1)
1677 for (int j = editLastRes; j > startres; j--)
1679 deleteChar(startres, groupSeqs, fixedRight);
1684 appendEdit(Action.DELETE_GAP, groupSeqs, startres,
1685 editLastRes - startres, false);
1692 * editing a single sequence
1696 // dragging to the right
1697 if (fixedColumns && fixedRight != -1)
1699 for (int j = editLastRes; j < startres; j++)
1701 if (!insertGap(j, seqs, fixedRight))
1704 * e.g. cursor mode command specified
1705 * more inserts than are possible
1713 appendEdit(Action.INSERT_GAP, seqs, editLastRes,
1714 startres - editLastRes, false);
1721 // dragging to the left
1722 if (fixedColumns && fixedRight != -1)
1724 for (int j = editLastRes; j > startres; j--)
1726 if (!Comparison.isGap(seq.getCharAt(startres)))
1730 deleteChar(startres, seqs, fixedRight);
1735 // could be a keyboard edit trying to delete none gaps
1737 for (int m = startres; m < editLastRes; m++)
1739 if (!Comparison.isGap(seq.getCharAt(m)))
1747 appendEdit(Action.DELETE_GAP, seqs, startres, max, false);
1752 {// insertGap==false AND editSeq==TRUE;
1753 if (fixedColumns && fixedRight != -1)
1755 for (int j = editLastRes; j < startres; j++)
1757 insertGap(j, seqs, fixedRight);
1762 appendEdit(Action.INSERT_NUC, seqs, editLastRes,
1763 startres - editLastRes, false);
1773 * Constructs an informative status bar message while dragging to insert or
1774 * delete gaps. Answers null if inserts and deletes cancel out.
1776 * @param editCommand
1777 * a command containing the list of individual edits
1780 protected static String getEditStatusMessage(EditCommand editCommand)
1782 if (editCommand == null)
1788 * add any inserts, and subtract any deletes,
1789 * not counting those auto-inserted when doing a 'locked edit'
1790 * (so only counting edits 'under the cursor')
1793 for (Edit cmd : editCommand.getEdits())
1795 if (!cmd.isSystemGenerated())
1797 count += cmd.getAction() == Action.INSERT_GAP ? cmd.getNumber()
1805 * inserts and deletes cancel out
1810 String msgKey = count > 1 ? "label.insert_gaps"
1811 : (count == 1 ? "label.insert_gap"
1812 : (count == -1 ? "label.delete_gap"
1813 : "label.delete_gaps"));
1814 count = Math.abs(count);
1816 return MessageManager.formatMessage(msgKey, String.valueOf(count));
1820 * Inserts one gap at column j, deleting the right-most gapped column up to
1821 * (and including) fixedColumn. Returns true if the edit is successful, false
1822 * if no blank column is available to allow the insertion to be balanced by a
1827 * @param fixedColumn
1830 boolean insertGap(int j, SequenceI[] seq, int fixedColumn)
1832 int blankColumn = fixedColumn;
1833 for (int s = 0; s < seq.length; s++)
1835 // Find the next gap before the end of the visible region boundary
1836 // If lastCol > j, theres a boundary after the gap insertion
1838 for (blankColumn = fixedColumn; blankColumn > j; blankColumn--)
1840 if (Comparison.isGap(seq[s].getCharAt(blankColumn)))
1842 // Theres a space, so break and insert the gap
1847 if (blankColumn <= j)
1849 blankColumn = fixedColumn;
1855 appendEdit(Action.DELETE_GAP, seq, blankColumn, 1, true);
1857 appendEdit(Action.INSERT_GAP, seq, j, 1, false);
1863 * Helper method to add and perform one edit action
1869 * @param systemGenerated
1870 * true if the edit is a 'balancing' delete (or insert) to match a
1871 * user's insert (or delete) in a locked editing region
1873 protected void appendEdit(Action action, SequenceI[] seq, int pos,
1874 int count, boolean systemGenerated)
1877 final Edit edit = new EditCommand().new Edit(action, seq, pos, count,
1878 av.getAlignment().getGapCharacter());
1879 edit.setSystemGenerated(systemGenerated);
1881 editCommand.appendEdit(edit, av.getAlignment(), true, null);
1885 * Deletes the character at column j, and inserts a gap at fixedColumn, in
1886 * each of the given sequences. The caller should ensure that all sequences
1887 * are gapped in column j.
1891 * @param fixedColumn
1893 void deleteChar(int j, SequenceI[] seqs, int fixedColumn)
1895 appendEdit(Action.DELETE_GAP, seqs, j, 1, false);
1897 appendEdit(Action.INSERT_GAP, seqs, fixedColumn, 1, true);
1907 public void mouseEntered(MouseEvent e)
1914 if ((scrollThread != null) && (scrollThread.isRunning()))
1916 scrollThread.stopScrolling();
1917 scrollThread = null;
1928 public void mouseExited(MouseEvent e)
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.recalcConservation(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 if (res >= av.getAlignment().getWidth())
2253 res = av.getAlignment().getWidth() - 1;
2256 if (stretchGroup.getEndRes() == res)
2258 // Edit end res position of selected group
2259 changeEndRes = true;
2261 else if (stretchGroup.getStartRes() == res)
2263 // Edit start res position of selected group
2264 changeStartRes = true;
2267 if (res < av.getRanges().getStartRes())
2269 res = av.getRanges().getStartRes();
2274 if (res > (stretchGroup.getStartRes() - 1))
2276 stretchGroup.setEndRes(res);
2277 updateOverviewAndStructs |= av.isSelectionDefinedGroup();
2280 else if (changeStartRes)
2282 if (res < (stretchGroup.getEndRes() + 1))
2284 stretchGroup.setStartRes(res);
2285 updateOverviewAndStructs |= av.isSelectionDefinedGroup();
2289 int dragDirection = 0;
2295 else if (y < oldSeq)
2300 while ((y != oldSeq) && (oldSeq > -1)
2301 && (y < av.getAlignment().getHeight()))
2303 // This routine ensures we don't skip any sequences, as the
2304 // selection is quite slow.
2305 Sequence seq = (Sequence) av.getAlignment().getSequenceAt(oldSeq);
2307 oldSeq += dragDirection;
2314 Sequence nextSeq = (Sequence) av.getAlignment().getSequenceAt(oldSeq);
2316 if (stretchGroup.getSequences(null).contains(nextSeq))
2318 stretchGroup.deleteSequence(seq, false);
2319 updateOverviewAndStructs |= av.isSelectionDefinedGroup();
2325 stretchGroup.addSequence(seq, false);
2328 stretchGroup.addSequence(nextSeq, false);
2329 updateOverviewAndStructs |= av.isSelectionDefinedGroup();
2338 mouseDragging = true;
2340 if ((scrollThread != null) && (scrollThread.isRunning()))
2342 scrollThread.setEvent(evt);
2346 * construct a status message showing the range of the selection
2348 StringBuilder status = new StringBuilder(64);
2349 List<SequenceI> seqs = stretchGroup.getSequences();
2350 String name = seqs.get(0).getName();
2351 if (name.length() > 20)
2353 name = name.substring(0, 20);
2355 status.append(name).append(" - ");
2356 name = seqs.get(seqs.size() - 1).getName();
2357 if (name.length() > 20)
2359 name = name.substring(0, 20);
2361 status.append(name).append(" ");
2362 int startRes = stretchGroup.getStartRes();
2363 status.append(" cols ").append(String.valueOf(startRes + 1))
2365 int endRes = stretchGroup.getEndRes();
2366 status.append(String.valueOf(endRes + 1));
2367 status.append(" (").append(String.valueOf(seqs.size())).append(" x ")
2368 .append(String.valueOf(endRes - startRes + 1)).append(")");
2369 ap.alignFrame.setStatus(status.toString());
2372 void scrollCanvas(MouseEvent evt)
2376 if ((scrollThread != null) && (scrollThread.isRunning()))
2378 scrollThread.stopScrolling();
2379 scrollThread = null;
2381 mouseDragging = false;
2385 if (scrollThread == null)
2387 scrollThread = new ScrollThread();
2390 mouseDragging = true;
2391 scrollThread.setEvent(evt);
2396 // this class allows scrolling off the bottom of the visible alignment
2397 class ScrollThread extends Thread
2401 private volatile boolean threadRunning = true;
2403 public ScrollThread()
2408 public void setEvent(MouseEvent e)
2413 public void stopScrolling()
2415 threadRunning = false;
2418 public boolean isRunning()
2420 return threadRunning;
2426 while (threadRunning)
2430 if (mouseDragging && (evt.getY() < 0)
2431 && (av.getRanges().getStartSeq() > 0))
2433 av.getRanges().scrollUp(true);
2436 if (mouseDragging && (evt.getY() >= getHeight()) && (av
2437 .getAlignment().getHeight() > av.getRanges().getEndSeq()))
2439 av.getRanges().scrollUp(false);
2442 if (mouseDragging && (evt.getX() < 0))
2444 av.getRanges().scrollRight(false);
2446 else if (mouseDragging && (evt.getX() >= getWidth()))
2448 av.getRanges().scrollRight(true);
2455 } catch (Exception ex)
2463 * modify current selection according to a received message.
2466 public void selection(SequenceGroup seqsel, ColumnSelection colsel,
2467 HiddenColumns hidden, SelectionSource source)
2469 // TODO: fix this hack - source of messages is align viewport, but SeqPanel
2470 // handles selection messages...
2471 // TODO: extend config options to allow user to control if selections may be
2472 // shared between viewports.
2473 boolean iSentTheSelection = (av == source
2474 || (source instanceof AlignViewport
2475 && ((AlignmentViewport) source).getSequenceSetId()
2476 .equals(av.getSequenceSetId())));
2478 if (iSentTheSelection)
2480 // respond to our own event by updating dependent dialogs
2481 if (ap.getCalculationDialog() != null)
2483 ap.getCalculationDialog().validateCalcTypes();
2489 // process further ?
2490 if (!av.followSelection)
2496 * Ignore the selection if there is one of our own pending.
2498 if (av.isSelectionGroupChanged(false) || av.isColSelChanged(false))
2504 * Check for selection in a view of which this one is a dna/protein
2507 if (selectionFromTranslation(seqsel, colsel, hidden, source))
2512 // do we want to thread this ? (contention with seqsel and colsel locks, I
2515 * only copy colsel if there is a real intersection between
2516 * sequence selection and this panel's alignment
2518 boolean repaint = false;
2519 boolean copycolsel = false;
2521 SequenceGroup sgroup = null;
2522 if (seqsel != null && seqsel.getSize() > 0)
2524 if (av.getAlignment() == null)
2526 Cache.log.warn("alignviewport av SeqSetId=" + av.getSequenceSetId()
2527 + " ViewId=" + av.getViewId()
2528 + " 's alignment is NULL! returning immediately.");
2531 sgroup = seqsel.intersect(av.getAlignment(),
2532 (av.hasHiddenRows()) ? av.getHiddenRepSequences() : null);
2533 if ((sgroup != null && sgroup.getSize() > 0))
2538 if (sgroup != null && sgroup.getSize() > 0)
2540 av.setSelectionGroup(sgroup);
2544 av.setSelectionGroup(null);
2546 av.isSelectionGroupChanged(true);
2551 // the current selection is unset or from a previous message
2552 // so import the new colsel.
2553 if (colsel == null || colsel.isEmpty())
2555 if (av.getColumnSelection() != null)
2557 av.getColumnSelection().clear();
2563 // TODO: shift colSel according to the intersecting sequences
2564 if (av.getColumnSelection() == null)
2566 av.setColumnSelection(new ColumnSelection(colsel));
2570 av.getColumnSelection().setElementsFrom(colsel,
2571 av.getAlignment().getHiddenColumns());
2574 av.isColSelChanged(true);
2578 if (copycolsel && av.hasHiddenColumns()
2579 && (av.getAlignment().getHiddenColumns() == null))
2581 System.err.println("Bad things");
2583 if (repaint) // always true!
2585 // probably finessing with multiple redraws here
2586 PaintRefresher.Refresh(this, av.getSequenceSetId());
2587 // ap.paintAlignment(false);
2590 // lastly, update dependent dialogs
2591 if (ap.getCalculationDialog() != null)
2593 ap.getCalculationDialog().validateCalcTypes();
2599 * If this panel is a cdna/protein translation view of the selection source,
2600 * tries to map the source selection to a local one, and returns true. Else
2607 protected boolean selectionFromTranslation(SequenceGroup seqsel,
2608 ColumnSelection colsel, HiddenColumns hidden,
2609 SelectionSource source)
2611 if (!(source instanceof AlignViewportI))
2615 final AlignViewportI sourceAv = (AlignViewportI) source;
2616 if (sourceAv.getCodingComplement() != av
2617 && av.getCodingComplement() != sourceAv)
2623 * Map sequence selection
2625 SequenceGroup sg = MappingUtils.mapSequenceGroup(seqsel, sourceAv, av);
2626 av.setSelectionGroup(sg);
2627 av.isSelectionGroupChanged(true);
2630 * Map column selection
2632 // ColumnSelection cs = MappingUtils.mapColumnSelection(colsel, sourceAv,
2634 ColumnSelection cs = new ColumnSelection();
2635 HiddenColumns hs = new HiddenColumns();
2636 MappingUtils.mapColumnSelection(colsel, hidden, sourceAv, av, cs, hs);
2637 av.setColumnSelection(cs);
2638 boolean hiddenChanged = av.getAlignment().setHiddenColumns(hs);
2640 // lastly, update any dependent dialogs
2641 if (ap.getCalculationDialog() != null)
2643 ap.getCalculationDialog().validateCalcTypes();
2647 * repaint alignment, and also Overview or Structure
2648 * if hidden column selection has changed
2650 ap.paintAlignment(hiddenChanged, hiddenChanged);
2657 * @return null or last search results handled by this panel
2659 public SearchResultsI getLastSearchResults()
2661 return lastSearchResults;