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 seqIndex = Math.min((y / charHeight) + av.getRanges().getStartSeq(),
318 alignmentHeight - 1);
321 return new MousePos(col, seqIndex, annIndex);
324 * Returns the aligned sequence position (base 0) at the mouse position, or
325 * the closest visible one
330 int findColumn(MouseEvent evt)
335 final int startRes = av.getRanges().getStartRes();
336 final int charWidth = av.getCharWidth();
338 if (av.getWrapAlignment())
340 int hgap = av.getCharHeight();
341 if (av.getScaleAboveWrapped())
343 hgap += av.getCharHeight();
346 int cHeight = av.getAlignment().getHeight() * av.getCharHeight()
347 + hgap + seqCanvas.getAnnotationHeight();
350 y = Math.max(0, y - hgap);
351 x -= seqCanvas.getLabelWidthWest();
354 // mouse is over left scale
358 int cwidth = seqCanvas.getWrappedCanvasWidth(this.getWidth());
363 if (x >= cwidth * charWidth)
365 // mouse is over right scale
369 wrappedBlock = y / cHeight;
370 wrappedBlock += startRes / cwidth;
371 // allow for wrapped view scrolled right (possible from Overview)
372 int startOffset = startRes % cwidth;
373 res = wrappedBlock * cwidth + startOffset
374 + Math.min(cwidth - 1, x / charWidth);
379 * make sure we calculate relative to visible alignment,
380 * rather than right-hand gutter
382 x = Math.min(x, seqCanvas.getX() + seqCanvas.getWidth());
383 res = (x / charWidth) + startRes;
384 res = Math.min(res, av.getRanges().getEndRes());
387 if (av.hasHiddenColumns())
389 res = av.getAlignment().getHiddenColumns()
390 .visibleToAbsoluteColumn(res);
397 * When all of a sequence of edits are complete, put the resulting edit list
398 * on the history stack (undo list), and reset flags for editing in progress.
404 if (editCommand != null && editCommand.getSize() > 0)
406 ap.alignFrame.addHistoryItem(editCommand);
407 av.firePropertyChange("alignment", null,
408 av.getAlignment().getSequences());
413 * Tidy up come what may...
418 groupEditing = false;
427 seqCanvas.cursorY = getKeyboardNo1() - 1;
428 scrollToVisible(true);
431 void setCursorColumn()
433 seqCanvas.cursorX = getKeyboardNo1() - 1;
434 scrollToVisible(true);
437 void setCursorRowAndColumn()
439 if (keyboardNo2 == null)
441 keyboardNo2 = new StringBuffer();
445 seqCanvas.cursorX = getKeyboardNo1() - 1;
446 seqCanvas.cursorY = getKeyboardNo2() - 1;
447 scrollToVisible(true);
451 void setCursorPosition()
453 SequenceI sequence = av.getAlignment().getSequenceAt(seqCanvas.cursorY);
455 seqCanvas.cursorX = sequence.findIndex(getKeyboardNo1()) - 1;
456 scrollToVisible(true);
459 void moveCursor(int dx, int dy)
461 seqCanvas.cursorX += dx;
462 seqCanvas.cursorY += dy;
464 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
466 if (av.hasHiddenColumns() && !hidden.isVisible(seqCanvas.cursorX))
468 int original = seqCanvas.cursorX - dx;
469 int maxWidth = av.getAlignment().getWidth();
471 if (!hidden.isVisible(seqCanvas.cursorX))
473 int visx = hidden.absoluteToVisibleColumn(seqCanvas.cursorX - dx);
474 int[] region = hidden.getRegionWithEdgeAtRes(visx);
476 if (region != null) // just in case
481 seqCanvas.cursorX = region[1] + 1;
486 seqCanvas.cursorX = region[0] - 1;
489 seqCanvas.cursorX = (seqCanvas.cursorX < 0) ? 0 : seqCanvas.cursorX;
492 if (seqCanvas.cursorX >= maxWidth
493 || !hidden.isVisible(seqCanvas.cursorX))
495 seqCanvas.cursorX = original;
499 scrollToVisible(false);
503 * Scroll to make the cursor visible in the viewport.
506 * just jump to the location rather than scrolling
508 void scrollToVisible(boolean jump)
510 if (seqCanvas.cursorX < 0)
512 seqCanvas.cursorX = 0;
514 else if (seqCanvas.cursorX > av.getAlignment().getWidth() - 1)
516 seqCanvas.cursorX = av.getAlignment().getWidth() - 1;
519 if (seqCanvas.cursorY < 0)
521 seqCanvas.cursorY = 0;
523 else if (seqCanvas.cursorY > av.getAlignment().getHeight() - 1)
525 seqCanvas.cursorY = av.getAlignment().getHeight() - 1;
530 boolean repaintNeeded = true;
533 // only need to repaint if the viewport did not move, as otherwise it will
535 repaintNeeded = !av.getRanges().setViewportLocation(seqCanvas.cursorX,
540 if (av.getWrapAlignment())
542 // scrollToWrappedVisible expects x-value to have hidden cols subtracted
543 int x = av.getAlignment().getHiddenColumns()
544 .absoluteToVisibleColumn(seqCanvas.cursorX);
545 av.getRanges().scrollToWrappedVisible(x);
549 av.getRanges().scrollToVisible(seqCanvas.cursorX,
554 if (av.getAlignment().getHiddenColumns().isVisible(seqCanvas.cursorX))
556 setStatusMessage(av.getAlignment().getSequenceAt(seqCanvas.cursorY),
557 seqCanvas.cursorX, seqCanvas.cursorY);
567 void setSelectionAreaAtCursor(boolean topLeft)
569 SequenceI sequence = av.getAlignment().getSequenceAt(seqCanvas.cursorY);
571 if (av.getSelectionGroup() != null)
573 SequenceGroup sg = av.getSelectionGroup();
574 // Find the top and bottom of this group
575 int min = av.getAlignment().getHeight(), max = 0;
576 for (int i = 0; i < sg.getSize(); i++)
578 int index = av.getAlignment().findIndex(sg.getSequenceAt(i));
593 sg.setStartRes(seqCanvas.cursorX);
594 if (sg.getEndRes() < seqCanvas.cursorX)
596 sg.setEndRes(seqCanvas.cursorX);
599 min = seqCanvas.cursorY;
603 sg.setEndRes(seqCanvas.cursorX);
604 if (sg.getStartRes() > seqCanvas.cursorX)
606 sg.setStartRes(seqCanvas.cursorX);
609 max = seqCanvas.cursorY + 1;
614 // Only the user can do this
615 av.setSelectionGroup(null);
619 // Now add any sequences between min and max
620 sg.getSequences(null).clear();
621 for (int i = min; i < max; i++)
623 sg.addSequence(av.getAlignment().getSequenceAt(i), false);
628 if (av.getSelectionGroup() == null)
630 SequenceGroup sg = new SequenceGroup();
631 sg.setStartRes(seqCanvas.cursorX);
632 sg.setEndRes(seqCanvas.cursorX);
633 sg.addSequence(sequence, false);
634 av.setSelectionGroup(sg);
637 ap.paintAlignment(false, false);
641 void insertGapAtCursor(boolean group)
643 groupEditing = group;
644 editStartSeq = seqCanvas.cursorY;
645 editLastRes = seqCanvas.cursorX;
646 editSequence(true, false, seqCanvas.cursorX + getKeyboardNo1());
650 void deleteGapAtCursor(boolean group)
652 groupEditing = group;
653 editStartSeq = seqCanvas.cursorY;
654 editLastRes = seqCanvas.cursorX + getKeyboardNo1();
655 editSequence(false, false, seqCanvas.cursorX);
659 void insertNucAtCursor(boolean group, String nuc)
661 // TODO not called - delete?
662 groupEditing = group;
663 editStartSeq = seqCanvas.cursorY;
664 editLastRes = seqCanvas.cursorX;
665 editSequence(false, true, seqCanvas.cursorX + getKeyboardNo1());
669 void numberPressed(char value)
671 if (keyboardNo1 == null)
673 keyboardNo1 = new StringBuffer();
676 if (keyboardNo2 != null)
678 keyboardNo2.append(value);
682 keyboardNo1.append(value);
690 if (keyboardNo1 != null)
692 int value = Integer.parseInt(keyboardNo1.toString());
696 } catch (Exception x)
707 if (keyboardNo2 != null)
709 int value = Integer.parseInt(keyboardNo2.toString());
713 } catch (Exception x)
727 public void mouseReleased(MouseEvent evt)
729 MousePos pos = findMousePosition(evt);
730 if (pos.isOverAnnotation() || pos.seqIndex == -1 || pos.column == -1)
735 boolean didDrag = mouseDragging; // did we come here after a drag
736 mouseDragging = false;
737 mouseWheelPressed = false;
739 if (evt.isPopupTrigger()) // Windows: mouseReleased
741 showPopupMenu(evt, pos);
752 doMouseReleasedDefineMode(evt, didDrag);
763 public void mousePressed(MouseEvent evt)
765 lastMousePress = evt.getPoint();
766 MousePos pos = findMousePosition(evt);
767 if (pos.isOverAnnotation() || pos.seqIndex == -1 || pos.column == -1)
772 if (SwingUtilities.isMiddleMouseButton(evt))
774 mouseWheelPressed = true;
778 boolean isControlDown = Platform.isControlDown(evt);
779 if (evt.isShiftDown() || isControlDown)
789 doMousePressedDefineMode(evt, pos);
793 int seq = pos.seqIndex;
794 int res = pos.column;
796 if ((seq < av.getAlignment().getHeight())
797 && (res < av.getAlignment().getSequenceAt(seq).getLength()))
814 public void mouseOverSequence(SequenceI sequence, int index, int pos)
816 String tmp = sequence.hashCode() + " " + index + " " + pos;
818 if (lastMessage == null || !lastMessage.equals(tmp))
820 // System.err.println("mouseOver Sequence: "+tmp);
821 ssm.mouseOverSequence(sequence, index, pos, av);
827 * Highlight the mapped region described by the search results object (unless
828 * unchanged). This supports highlight of protein while mousing over linked
829 * cDNA and vice versa. The status bar is also updated to show the location of
830 * the start of the highlighted region.
833 public void highlightSequence(SearchResultsI results)
835 if (results == null || results.equals(lastSearchResults))
839 lastSearchResults = results;
841 boolean wasScrolled = false;
843 if (av.isFollowHighlight())
845 // don't allow highlight of protein/cDNA to also scroll a complementary
846 // panel,as this sets up a feedback loop (scrolling panel 1 causes moused
847 // over residue to change abruptly, causing highlighted residue in panel 2
848 // to change, causing a scroll in panel 1 etc)
849 ap.setToScrollComplementPanel(false);
850 wasScrolled = ap.scrollToPosition(results);
853 seqCanvas.revalidate();
855 ap.setToScrollComplementPanel(true);
858 boolean noFastPaint = wasScrolled && av.getWrapAlignment();
859 if (seqCanvas.highlightSearchResults(results, noFastPaint))
861 setStatusMessage(results);
866 public VamsasSource getVamsasSource()
868 return this.ap == null ? null : this.ap.av;
872 public void updateColours(SequenceI seq, int index)
874 System.out.println("update the seqPanel colours");
879 * Action on mouse movement is to update the status bar to show the current
880 * sequence position, and (if features are shown) to show any features at the
881 * position in a tooltip. Does nothing if the mouse move does not change
887 public void mouseMoved(MouseEvent evt)
891 // This is because MacOSX creates a mouseMoved
892 // If control is down, other platforms will not.
896 final MousePos mousePos = findMousePosition(evt);
897 if (mousePos.equals(lastMousePosition))
900 * just a pixel move without change of 'cell'
904 lastMousePosition = mousePos;
906 if (mousePos.isOverAnnotation())
908 mouseMovedOverAnnotation(mousePos);
911 final int seq = mousePos.seqIndex;
913 final int column = mousePos.column;
914 if (column < 0 || seq < 0 || seq >= av.getAlignment().getHeight())
916 lastMousePosition = null;
917 setToolTipText(null);
919 ap.alignFrame.setStatus("");
923 SequenceI sequence = av.getAlignment().getSequenceAt(seq);
925 if (column >= sequence.getLength())
931 * set status bar message, returning residue position in sequence
933 boolean isGapped = Comparison.isGap(sequence.getCharAt(column));
934 final int pos = setStatusMessage(sequence, column, seq);
935 if (ssm != null && !isGapped)
937 mouseOverSequence(sequence, column, pos);
940 tooltipText.setLength(6); // Cuts the buffer back to <html>
942 SequenceGroup[] groups = av.getAlignment().findAllGroups(sequence);
945 for (int g = 0; g < groups.length; g++)
947 if (groups[g].getStartRes() <= column
948 && groups[g].getEndRes() >= column)
950 if (!groups[g].getName().startsWith("JTreeGroup")
951 && !groups[g].getName().startsWith("JGroup"))
953 tooltipText.append(groups[g].getName());
956 if (groups[g].getDescription() != null)
958 tooltipText.append(": " + groups[g].getDescription());
965 * add any features at the position to the tooltip; if over a gap, only
966 * add features that straddle the gap (pos may be the residue before or
969 if (av.isShowSequenceFeatures())
971 List<SequenceFeature> features = ap.getFeatureRenderer()
972 .findFeaturesAtColumn(sequence, column + 1);
973 seqARep.appendFeatures(tooltipText, pos, features,
974 this.ap.getSeqPanel().seqCanvas.fr);
976 if (tooltipText.length() == 6) // <html>
978 setToolTipText(null);
983 if (tooltipText.length() > MAX_TOOLTIP_LENGTH) // constant
985 tooltipText.setLength(MAX_TOOLTIP_LENGTH);
986 tooltipText.append("...");
988 String textString = tooltipText.toString();
989 if (lastTooltip == null || !lastTooltip.equals(textString))
991 String formattedTooltipText = JvSwingUtils.wrapTooltip(true,
993 setToolTipText(formattedTooltipText);
994 lastTooltip = textString;
1000 * When the view is in wrapped mode, and the mouse is over an annotation row,
1001 * shows the corresponding tooltip and status message (if any)
1006 protected void mouseMovedOverAnnotation(MousePos pos)
1008 final int column = pos.column;
1009 final int rowIndex = pos.annotationIndex;
1011 if (column < 0 || !av.getWrapAlignment() || !av.isShowAnnotation()
1016 AlignmentAnnotation[] anns = av.getAlignment().getAlignmentAnnotation();
1018 String tooltip = AnnotationPanel.buildToolTip(anns[rowIndex], column,
1020 setToolTipText(tooltip);
1021 lastTooltip = tooltip;
1023 String msg = AnnotationPanel.getStatusMessage(av.getAlignment(), column,
1025 ap.alignFrame.setStatus(msg);
1028 private Point lastp = null;
1033 * @see javax.swing.JComponent#getToolTipLocation(java.awt.event.MouseEvent)
1036 public Point getToolTipLocation(MouseEvent event)
1038 if (tooltipText == null || tooltipText.length() <= 6)
1044 int x = event.getX();
1046 // switch sides when tooltip is too close to edge
1047 int wdth = (w - x < 200) ? -(w / 2) : 5;
1049 if (!event.isShiftDown() || p == null)
1051 p = new Point(event.getX() + wdth, event.getY() - 20);
1055 * TODO: try to set position so region is not obscured by tooltip
1063 * set when the current UI interaction has resulted in a change that requires
1064 * shading in overviews and structures to be recalculated. this could be
1065 * changed to a something more expressive that indicates what actually has
1066 * changed, so selective redraws can be applied (ie. only structures, only
1069 private boolean updateOverviewAndStructs = false; // TODO: refactor to avcontroller
1072 * set if av.getSelectionGroup() refers to a group that is defined on the
1073 * alignment view, rather than a transient selection
1075 // private boolean editingDefinedGroup = false; // TODO: refactor to
1076 // avcontroller or viewModel
1079 * Sets the status message in alignment panel, showing the sequence number
1080 * (index) and id, and residue and residue position if not at a gap, for the
1081 * given sequence and column position. Returns the residue position returned
1082 * by Sequence.findPosition. Note this may be for the nearest adjacent residue
1083 * if at a gapped position.
1086 * aligned sequence object
1090 * index of sequence in alignment
1091 * @return sequence position of residue at column, or adjacent residue if at a
1094 int setStatusMessage(SequenceI sequence, final int column, int seqIndex)
1096 char sequenceChar = sequence.getCharAt(column);
1097 int pos = sequence.findPosition(column);
1098 setStatusMessage(sequence, seqIndex, sequenceChar, pos);
1104 * Builds the status message for the current cursor location and writes it to
1105 * the status bar, for example
1108 * Sequence 3 ID: FER1_SOLLC
1109 * Sequence 5 ID: FER1_PEA Residue: THR (4)
1110 * Sequence 5 ID: FER1_PEA Residue: B (3)
1111 * Sequence 6 ID: O.niloticus.3 Nucleotide: Uracil (2)
1116 * sequence position in the alignment (1..)
1117 * @param sequenceChar
1118 * the character under the cursor
1120 * the sequence residue position (if not over a gap)
1122 protected void setStatusMessage(SequenceI sequence, int seqIndex,
1123 char sequenceChar, int residuePos)
1125 StringBuilder text = new StringBuilder(32);
1128 * Sequence number (if known), and sequence name.
1130 String seqno = seqIndex == -1 ? "" : " " + (seqIndex + 1);
1131 text.append("Sequence").append(seqno).append(" ID: ")
1132 .append(sequence.getName());
1134 String residue = null;
1137 * Try to translate the display character to residue name (null for gap).
1139 boolean isGapped = Comparison.isGap(sequenceChar);
1143 boolean nucleotide = av.getAlignment().isNucleotide();
1144 String displayChar = String.valueOf(sequenceChar);
1147 residue = ResidueProperties.nucleotideName.get(displayChar);
1151 residue = "X".equalsIgnoreCase(displayChar) ? "X"
1152 : ("*".equals(displayChar) ? "STOP"
1153 : ResidueProperties.aa2Triplet.get(displayChar));
1155 text.append(" ").append(nucleotide ? "Nucleotide" : "Residue")
1156 .append(": ").append(residue == null ? displayChar : residue);
1158 text.append(" (").append(Integer.toString(residuePos)).append(")");
1160 ap.alignFrame.setStatus(text.toString());
1164 * Set the status bar message to highlight the first matched position in
1169 private void setStatusMessage(SearchResultsI results)
1171 AlignmentI al = this.av.getAlignment();
1172 int sequenceIndex = al.findIndex(results);
1173 if (sequenceIndex == -1)
1177 SequenceI ds = al.getSequenceAt(sequenceIndex).getDatasetSequence();
1178 for (SearchResultMatchI m : results.getResults())
1180 SequenceI seq = m.getSequence();
1181 if (seq.getDatasetSequence() != null)
1183 seq = seq.getDatasetSequence();
1188 int start = m.getStart();
1189 setStatusMessage(seq, sequenceIndex, seq.getCharAt(start - 1),
1200 public void mouseDragged(MouseEvent evt)
1202 MousePos pos = findMousePosition(evt);
1203 if (pos.isOverAnnotation() || pos.column == -1)
1208 if (mouseWheelPressed)
1210 boolean inSplitFrame = ap.av.getCodingComplement() != null;
1211 boolean copyChanges = inSplitFrame && av.isProteinFontAsCdna();
1213 int oldWidth = av.getCharWidth();
1215 // Which is bigger, left-right or up-down?
1216 if (Math.abs(evt.getY() - lastMousePress.getY()) > Math
1217 .abs(evt.getX() - lastMousePress.getX()))
1220 * on drag up or down, decrement or increment font size
1222 int fontSize = av.font.getSize();
1223 boolean fontChanged = false;
1225 if (evt.getY() < lastMousePress.getY())
1230 else if (evt.getY() > lastMousePress.getY())
1243 Font newFont = new Font(av.font.getName(), av.font.getStyle(),
1245 av.setFont(newFont, true);
1246 av.setCharWidth(oldWidth);
1250 ap.av.getCodingComplement().setFont(newFont, true);
1251 SplitFrame splitFrame = (SplitFrame) ap.alignFrame
1252 .getSplitViewContainer();
1253 splitFrame.adjustLayout();
1254 splitFrame.repaint();
1261 * on drag left or right, decrement or increment character width
1264 if (evt.getX() < lastMousePress.getX() && av.getCharWidth() > 1)
1266 newWidth = av.getCharWidth() - 1;
1267 av.setCharWidth(newWidth);
1269 else if (evt.getX() > lastMousePress.getX())
1271 newWidth = av.getCharWidth() + 1;
1272 av.setCharWidth(newWidth);
1276 ap.paintAlignment(false, false);
1280 * need to ensure newWidth is set on cdna, regardless of which
1281 * panel the mouse drag happened in; protein will compute its
1282 * character width as 1:1 or 3:1
1284 av.getCodingComplement().setCharWidth(newWidth);
1285 SplitFrame splitFrame = (SplitFrame) ap.alignFrame
1286 .getSplitViewContainer();
1287 splitFrame.adjustLayout();
1288 splitFrame.repaint();
1293 FontMetrics fm = getFontMetrics(av.getFont());
1294 av.validCharWidth = fm.charWidth('M') <= av.getCharWidth();
1296 lastMousePress = evt.getPoint();
1303 dragStretchGroup(evt);
1307 int res = pos.column;
1314 if ((editLastRes == -1) || (editLastRes == res))
1319 if ((res < av.getAlignment().getWidth()) && (res < editLastRes))
1321 // dragLeft, delete gap
1322 editSequence(false, false, res);
1326 editSequence(true, false, res);
1329 mouseDragging = true;
1330 if (scrollThread != null)
1332 scrollThread.setMousePosition(evt.getPoint());
1337 * Edits the sequence to insert or delete one or more gaps, in response to a
1338 * mouse drag or cursor mode command. The number of inserts/deletes may be
1339 * specified with the cursor command, or else depends on the mouse event
1340 * (normally one column, but potentially more for a fast mouse drag).
1342 * Delete gaps is limited to the number of gaps left of the cursor position
1343 * (mouse drag), or at or right of the cursor position (cursor mode).
1345 * In group editing mode (Ctrl or Cmd down), the edit acts on all sequences in
1346 * the current selection group.
1348 * In locked editing mode (with a selection group present), inserts/deletions
1349 * within the selection group are limited to its boundaries (and edits outside
1350 * the group stop at its border).
1353 * true to insert gaps, false to delete gaps
1355 * (unused parameter)
1357 * the column at which to perform the action; the number of columns
1358 * affected depends on <code>this.editLastRes</code> (cursor column
1361 synchronized void editSequence(boolean insertGap, boolean editSeq,
1365 int fixedRight = -1;
1366 boolean fixedColumns = false;
1367 SequenceGroup sg = av.getSelectionGroup();
1369 final SequenceI seq = av.getAlignment().getSequenceAt(editStartSeq);
1371 // No group, but the sequence may represent a group
1372 if (!groupEditing && av.hasHiddenRows())
1374 if (av.isHiddenRepSequence(seq))
1376 sg = av.getRepresentedSequences(seq);
1377 groupEditing = true;
1381 StringBuilder message = new StringBuilder(64); // for status bar
1384 * make a name for the edit action, for
1385 * status bar message and Undo/Redo menu
1387 String label = null;
1390 message.append("Edit group:");
1391 label = MessageManager.getString("action.edit_group");
1395 message.append("Edit sequence: " + seq.getName());
1396 label = seq.getName();
1397 if (label.length() > 10)
1399 label = label.substring(0, 10);
1401 label = MessageManager.formatMessage("label.edit_params",
1407 * initialise the edit command if there is not
1408 * already one being extended
1410 if (editCommand == null)
1412 editCommand = new EditCommand(label);
1417 message.append(" insert ");
1421 message.append(" delete ");
1424 message.append(Math.abs(startres - editLastRes) + " gaps.");
1425 ap.alignFrame.setStatus(message.toString());
1428 * is there a selection group containing the sequence being edited?
1429 * if so the boundary of the group is the limit of the edit
1430 * (but the edit may be inside or outside the selection group)
1432 boolean inSelectionGroup = sg != null
1433 && sg.getSequences(av.getHiddenRepSequences()).contains(seq);
1434 if (groupEditing || inSelectionGroup)
1436 fixedColumns = true;
1438 // sg might be null as the user may only see 1 sequence,
1439 // but the sequence represents a group
1442 if (!av.isHiddenRepSequence(seq))
1447 sg = av.getRepresentedSequences(seq);
1450 fixedLeft = sg.getStartRes();
1451 fixedRight = sg.getEndRes();
1453 if ((startres < fixedLeft && editLastRes >= fixedLeft)
1454 || (startres >= fixedLeft && editLastRes < fixedLeft)
1455 || (startres > fixedRight && editLastRes <= fixedRight)
1456 || (startres <= fixedRight && editLastRes > fixedRight))
1462 if (fixedLeft > startres)
1464 fixedRight = fixedLeft - 1;
1467 else if (fixedRight < startres)
1469 fixedLeft = fixedRight;
1474 if (av.hasHiddenColumns())
1476 fixedColumns = true;
1477 int y1 = av.getAlignment().getHiddenColumns()
1478 .getNextHiddenBoundary(true, startres);
1479 int y2 = av.getAlignment().getHiddenColumns()
1480 .getNextHiddenBoundary(false, startres);
1482 if ((insertGap && startres > y1 && editLastRes < y1)
1483 || (!insertGap && startres < y2 && editLastRes > y2))
1489 // System.out.print(y1+" "+y2+" "+fixedLeft+" "+fixedRight+"~~");
1490 // Selection spans a hidden region
1491 if (fixedLeft < y1 && (fixedRight > y2 || fixedRight == -1))
1499 fixedRight = y2 - 1;
1504 boolean success = doEditSequence(insertGap, editSeq, startres,
1505 fixedRight, fixedColumns, sg);
1508 * report what actually happened (might be less than
1509 * what was requested), by inspecting the edit commands added
1511 String msg = getEditStatusMessage(editCommand);
1512 ap.alignFrame.setStatus(msg == null ? " " : msg);
1518 editLastRes = startres;
1519 seqCanvas.repaint();
1523 * A helper method that performs the requested editing to insert or delete
1524 * gaps (if possible). Answers true if the edit was successful, false if could
1525 * only be performed in part or not at all. Failure may occur in 'locked edit'
1526 * mode, when an insertion requires a matching gapped position (or column) to
1527 * delete, and deletion requires an adjacent gapped position (or column) to
1531 * true if inserting gap(s), false if deleting
1533 * (unused parameter, currently always false)
1535 * the column at which to perform the edit
1537 * fixed right boundary column of a locked edit (within or to the
1538 * left of a selection group)
1539 * @param fixedColumns
1540 * true if this is a locked edit
1542 * the sequence group (if group edit is being performed)
1545 protected boolean doEditSequence(final boolean insertGap,
1546 final boolean editSeq, final int startres, int fixedRight,
1547 final boolean fixedColumns, final SequenceGroup sg)
1549 final SequenceI seq = av.getAlignment().getSequenceAt(editStartSeq);
1550 SequenceI[] seqs = new SequenceI[] { seq };
1554 List<SequenceI> vseqs = sg.getSequences(av.getHiddenRepSequences());
1555 int g, groupSize = vseqs.size();
1556 SequenceI[] groupSeqs = new SequenceI[groupSize];
1557 for (g = 0; g < groupSeqs.length; g++)
1559 groupSeqs[g] = vseqs.get(g);
1565 // If the user has selected the whole sequence, and is dragging to
1566 // the right, we can still extend the alignment and selectionGroup
1567 if (sg.getStartRes() == 0 && sg.getEndRes() == fixedRight
1568 && sg.getEndRes() == av.getAlignment().getWidth() - 1)
1571 av.getAlignment().getWidth() + startres - editLastRes);
1572 fixedRight = sg.getEndRes();
1575 // Is it valid with fixed columns??
1576 // Find the next gap before the end
1577 // of the visible region boundary
1578 boolean blank = false;
1579 for (; fixedRight > editLastRes; fixedRight--)
1583 for (g = 0; g < groupSize; g++)
1585 for (int j = 0; j < startres - editLastRes; j++)
1588 .isGap(groupSeqs[g].getCharAt(fixedRight - j)))
1603 if (sg.getSize() == av.getAlignment().getHeight())
1605 if ((av.hasHiddenColumns()
1606 && startres < av.getAlignment().getHiddenColumns()
1607 .getNextHiddenBoundary(false, startres)))
1612 int alWidth = av.getAlignment().getWidth();
1613 if (av.hasHiddenRows())
1615 int hwidth = av.getAlignment().getHiddenSequences()
1617 if (hwidth > alWidth)
1622 // We can still insert gaps if the selectionGroup
1623 // contains all the sequences
1624 sg.setEndRes(sg.getEndRes() + startres - editLastRes);
1625 fixedRight = alWidth + startres - editLastRes;
1635 else if (!insertGap)
1637 // / Are we able to delete?
1638 // ie are all columns blank?
1640 for (g = 0; g < groupSize; g++)
1642 for (int j = startres; j < editLastRes; j++)
1644 if (groupSeqs[g].getLength() <= j)
1649 if (!Comparison.isGap(groupSeqs[g].getCharAt(j)))
1651 // Not a gap, block edit not valid
1660 // dragging to the right
1661 if (fixedColumns && fixedRight != -1)
1663 for (int j = editLastRes; j < startres; j++)
1665 insertGap(j, groupSeqs, fixedRight);
1670 appendEdit(Action.INSERT_GAP, groupSeqs, startres,
1671 startres - editLastRes, false);
1676 // dragging to the left
1677 if (fixedColumns && fixedRight != -1)
1679 for (int j = editLastRes; j > startres; j--)
1681 deleteChar(startres, groupSeqs, fixedRight);
1686 appendEdit(Action.DELETE_GAP, groupSeqs, startres,
1687 editLastRes - startres, false);
1694 * editing a single sequence
1698 // dragging to the right
1699 if (fixedColumns && fixedRight != -1)
1701 for (int j = editLastRes; j < startres; j++)
1703 if (!insertGap(j, seqs, fixedRight))
1706 * e.g. cursor mode command specified
1707 * more inserts than are possible
1715 appendEdit(Action.INSERT_GAP, seqs, editLastRes,
1716 startres - editLastRes, false);
1723 // dragging to the left
1724 if (fixedColumns && fixedRight != -1)
1726 for (int j = editLastRes; j > startres; j--)
1728 if (!Comparison.isGap(seq.getCharAt(startres)))
1732 deleteChar(startres, seqs, fixedRight);
1737 // could be a keyboard edit trying to delete none gaps
1739 for (int m = startres; m < editLastRes; m++)
1741 if (!Comparison.isGap(seq.getCharAt(m)))
1749 appendEdit(Action.DELETE_GAP, seqs, startres, max, false);
1754 {// insertGap==false AND editSeq==TRUE;
1755 if (fixedColumns && fixedRight != -1)
1757 for (int j = editLastRes; j < startres; j++)
1759 insertGap(j, seqs, fixedRight);
1764 appendEdit(Action.INSERT_NUC, seqs, editLastRes,
1765 startres - editLastRes, false);
1775 * Constructs an informative status bar message while dragging to insert or
1776 * delete gaps. Answers null if inserts and deletes cancel out.
1778 * @param editCommand
1779 * a command containing the list of individual edits
1782 protected static String getEditStatusMessage(EditCommand editCommand)
1784 if (editCommand == null)
1790 * add any inserts, and subtract any deletes,
1791 * not counting those auto-inserted when doing a 'locked edit'
1792 * (so only counting edits 'under the cursor')
1795 for (Edit cmd : editCommand.getEdits())
1797 if (!cmd.isSystemGenerated())
1799 count += cmd.getAction() == Action.INSERT_GAP ? cmd.getNumber()
1807 * inserts and deletes cancel out
1812 String msgKey = count > 1 ? "label.insert_gaps"
1813 : (count == 1 ? "label.insert_gap"
1814 : (count == -1 ? "label.delete_gap"
1815 : "label.delete_gaps"));
1816 count = Math.abs(count);
1818 return MessageManager.formatMessage(msgKey, String.valueOf(count));
1822 * Inserts one gap at column j, deleting the right-most gapped column up to
1823 * (and including) fixedColumn. Returns true if the edit is successful, false
1824 * if no blank column is available to allow the insertion to be balanced by a
1829 * @param fixedColumn
1832 boolean insertGap(int j, SequenceI[] seq, int fixedColumn)
1834 int blankColumn = fixedColumn;
1835 for (int s = 0; s < seq.length; s++)
1837 // Find the next gap before the end of the visible region boundary
1838 // If lastCol > j, theres a boundary after the gap insertion
1840 for (blankColumn = fixedColumn; blankColumn > j; blankColumn--)
1842 if (Comparison.isGap(seq[s].getCharAt(blankColumn)))
1844 // Theres a space, so break and insert the gap
1849 if (blankColumn <= j)
1851 blankColumn = fixedColumn;
1857 appendEdit(Action.DELETE_GAP, seq, blankColumn, 1, true);
1859 appendEdit(Action.INSERT_GAP, seq, j, 1, false);
1865 * Helper method to add and perform one edit action
1871 * @param systemGenerated
1872 * true if the edit is a 'balancing' delete (or insert) to match a
1873 * user's insert (or delete) in a locked editing region
1875 protected void appendEdit(Action action, SequenceI[] seq, int pos,
1876 int count, boolean systemGenerated)
1879 final Edit edit = new EditCommand().new Edit(action, seq, pos, count,
1880 av.getAlignment().getGapCharacter());
1881 edit.setSystemGenerated(systemGenerated);
1883 editCommand.appendEdit(edit, av.getAlignment(), true, null);
1887 * Deletes the character at column j, and inserts a gap at fixedColumn, in
1888 * each of the given sequences. The caller should ensure that all sequences
1889 * are gapped in column j.
1893 * @param fixedColumn
1895 void deleteChar(int j, SequenceI[] seqs, int fixedColumn)
1897 appendEdit(Action.DELETE_GAP, seqs, j, 1, false);
1899 appendEdit(Action.INSERT_GAP, seqs, fixedColumn, 1, true);
1903 * On reentering the panel, stops any scrolling that was started on dragging
1909 public void mouseEntered(MouseEvent e)
1919 * On leaving the panel, if the mouse is being dragged, starts a thread to
1920 * scroll it until the mouse is released (in unwrapped mode only)
1925 public void mouseExited(MouseEvent e)
1927 ap.alignFrame.setStatus(" ");
1928 if (av.getWrapAlignment())
1933 if (mouseDragging && scrollThread == null)
1935 scrollThread = new ScrollThread();
1940 * Handler for double-click on a position with one or more sequence features.
1941 * Opens the Amend Features dialog to allow feature details to be amended, or
1942 * the feature deleted.
1945 public void mouseClicked(MouseEvent evt)
1947 SequenceGroup sg = null;
1948 MousePos pos = findMousePosition(evt);
1949 if (pos.isOverAnnotation() || pos.seqIndex == -1 || pos.column == -1)
1954 if (evt.getClickCount() > 1)
1956 sg = av.getSelectionGroup();
1957 if (sg != null && sg.getSize() == 1
1958 && sg.getEndRes() - sg.getStartRes() < 2)
1960 av.setSelectionGroup(null);
1963 int column = pos.column;
1966 * find features at the position (if not gapped), or straddling
1967 * the position (if at a gap)
1969 SequenceI sequence = av.getAlignment().getSequenceAt(pos.seqIndex);
1970 List<SequenceFeature> features = seqCanvas.getFeatureRenderer()
1971 .findFeaturesAtColumn(sequence, column + 1);
1973 if (!features.isEmpty())
1976 * highlight the first feature at the position on the alignment
1978 SearchResultsI highlight = new SearchResults();
1979 highlight.addResult(sequence, features.get(0).getBegin(), features
1981 seqCanvas.highlightSearchResults(highlight, false);
1984 * open the Amend Features dialog; clear highlighting afterwards,
1985 * whether changes were made or not
1987 List<SequenceI> seqs = Collections.singletonList(sequence);
1988 seqCanvas.getFeatureRenderer().amendFeatures(seqs, features, false,
1990 av.setSearchResults(null); // clear highlighting
1991 seqCanvas.repaint(); // draw new/amended features
1997 public void mouseWheelMoved(MouseWheelEvent e)
2000 double wheelRotation = e.getPreciseWheelRotation();
2001 if (wheelRotation > 0)
2003 if (e.isShiftDown())
2005 av.getRanges().scrollRight(true);
2010 av.getRanges().scrollUp(false);
2013 else if (wheelRotation < 0)
2015 if (e.isShiftDown())
2017 av.getRanges().scrollRight(false);
2021 av.getRanges().scrollUp(true);
2026 * update status bar and tooltip for new position
2027 * (need to synthesize a mouse movement to refresh tooltip)
2030 ToolTipManager.sharedInstance().mouseMoved(e);
2039 protected void doMousePressedDefineMode(MouseEvent evt, MousePos pos)
2041 if (pos.isOverAnnotation() || pos.seqIndex == -1 || pos.column == -1)
2046 final int res = pos.column;
2047 final int seq = pos.seqIndex;
2049 updateOverviewAndStructs = false;
2051 startWrapBlock = wrappedBlock;
2053 SequenceI sequence = av.getAlignment().getSequenceAt(seq);
2055 if ((sequence == null) || (res > sequence.getLength()))
2060 stretchGroup = av.getSelectionGroup();
2062 if (stretchGroup == null || !stretchGroup.contains(sequence, res))
2064 stretchGroup = av.getAlignment().findGroup(sequence, res);
2065 if (stretchGroup != null)
2067 // only update the current selection if the popup menu has a group to
2069 av.setSelectionGroup(stretchGroup);
2073 if (evt.isPopupTrigger()) // Mac: mousePressed
2075 showPopupMenu(evt, pos);
2080 * defer right-mouse click handling to mouseReleased on Windows
2081 * (where isPopupTrigger() will answer true)
2082 * NB isRightMouseButton is also true for Cmd-click on Mac
2084 if (SwingUtilities.isRightMouseButton(evt) && !Platform.isAMac())
2091 seqCanvas.cursorX = res;
2092 seqCanvas.cursorY = seq;
2093 seqCanvas.repaint();
2097 if (stretchGroup == null)
2099 createStretchGroup(res, sequence);
2102 if (stretchGroup != null)
2104 stretchGroup.addPropertyChangeListener(seqCanvas);
2107 seqCanvas.repaint();
2110 private void createStretchGroup(int res, SequenceI sequence)
2112 // Only if left mouse button do we want to change group sizes
2113 // define a new group here
2114 SequenceGroup sg = new SequenceGroup();
2115 sg.setStartRes(res);
2117 sg.addSequence(sequence, false);
2118 av.setSelectionGroup(sg);
2121 if (av.getConservationSelected())
2123 SliderPanel.setConservationSlider(ap, av.getResidueShading(),
2127 if (av.getAbovePIDThreshold())
2129 SliderPanel.setPIDSliderSource(ap, av.getResidueShading(),
2132 // TODO: stretchGroup will always be not null. Is this a merge error ?
2133 // or is there a threading issue here?
2134 if ((stretchGroup != null) && (stretchGroup.getEndRes() == res))
2136 // Edit end res position of selected group
2137 changeEndRes = true;
2139 else if ((stretchGroup != null) && (stretchGroup.getStartRes() == res))
2141 // Edit end res position of selected group
2142 changeStartRes = true;
2144 stretchGroup.getWidth();
2149 * Build and show a pop-up menu at the right-click mouse position
2154 void showPopupMenu(MouseEvent evt, MousePos pos)
2156 final int column = pos.column;
2157 final int seq = pos.seqIndex;
2158 SequenceI sequence = av.getAlignment().getSequenceAt(seq);
2159 List<SequenceFeature> features = ap.getFeatureRenderer()
2160 .findFeaturesAtColumn(sequence, column + 1);
2162 PopupMenu pop = new PopupMenu(ap, null, features);
2163 pop.show(this, evt.getX(), evt.getY());
2167 * Update the display after mouse up on a selection or group
2170 * mouse released event details
2172 * true if this event is happening after a mouse drag (rather than a
2175 protected void doMouseReleasedDefineMode(MouseEvent evt,
2178 if (stretchGroup == null)
2183 stretchGroup.removePropertyChangeListener(seqCanvas);
2185 // always do this - annotation has own state
2186 // but defer colourscheme update until hidden sequences are passed in
2187 boolean vischange = stretchGroup.recalcConservation(true);
2188 updateOverviewAndStructs |= vischange && av.isSelectionDefinedGroup()
2190 if (stretchGroup.cs != null)
2194 stretchGroup.cs.alignmentChanged(stretchGroup,
2195 av.getHiddenRepSequences());
2198 ResidueShaderI groupColourScheme = stretchGroup
2199 .getGroupColourScheme();
2200 String name = stretchGroup.getName();
2201 if (stretchGroup.cs.conservationApplied())
2203 SliderPanel.setConservationSlider(ap, groupColourScheme, name);
2205 if (stretchGroup.cs.getThreshold() > 0)
2207 SliderPanel.setPIDSliderSource(ap, groupColourScheme, name);
2210 PaintRefresher.Refresh(this, av.getSequenceSetId());
2211 // TODO: structure colours only need updating if stretchGroup used to or now
2212 // does contain sequences with structure views
2213 ap.paintAlignment(updateOverviewAndStructs, updateOverviewAndStructs);
2214 updateOverviewAndStructs = false;
2215 changeEndRes = false;
2216 changeStartRes = false;
2217 stretchGroup = null;
2222 * Resizes the borders of a selection group depending on the direction of
2227 protected void dragStretchGroup(MouseEvent evt)
2229 if (stretchGroup == null)
2234 MousePos pos = findMousePosition(evt);
2235 if (pos.isOverAnnotation() || pos.column == -1 || pos.seqIndex == -1)
2240 int res = pos.column;
2241 int y = pos.seqIndex;
2243 if (wrappedBlock != startWrapBlock)
2248 res = Math.min(res, av.getAlignment().getWidth()-1);
2250 if (stretchGroup.getEndRes() == res)
2252 // Edit end res position of selected group
2253 changeEndRes = true;
2255 else if (stretchGroup.getStartRes() == res)
2257 // Edit start res position of selected group
2258 changeStartRes = true;
2261 if (res < av.getRanges().getStartRes())
2263 res = av.getRanges().getStartRes();
2268 if (res > (stretchGroup.getStartRes() - 1))
2270 stretchGroup.setEndRes(res);
2271 updateOverviewAndStructs |= av.isSelectionDefinedGroup();
2274 else if (changeStartRes)
2276 if (res < (stretchGroup.getEndRes() + 1))
2278 stretchGroup.setStartRes(res);
2279 updateOverviewAndStructs |= av.isSelectionDefinedGroup();
2283 int dragDirection = 0;
2289 else if (y < oldSeq)
2294 while ((y != oldSeq) && (oldSeq > -1)
2295 && (y < av.getAlignment().getHeight()))
2297 // This routine ensures we don't skip any sequences, as the
2298 // selection is quite slow.
2299 Sequence seq = (Sequence) av.getAlignment().getSequenceAt(oldSeq);
2301 oldSeq += dragDirection;
2308 Sequence nextSeq = (Sequence) av.getAlignment().getSequenceAt(oldSeq);
2310 if (stretchGroup.getSequences(null).contains(nextSeq))
2312 stretchGroup.deleteSequence(seq, false);
2313 updateOverviewAndStructs |= av.isSelectionDefinedGroup();
2319 stretchGroup.addSequence(seq, false);
2322 stretchGroup.addSequence(nextSeq, false);
2323 updateOverviewAndStructs |= av.isSelectionDefinedGroup();
2332 mouseDragging = true;
2334 if (scrollThread != null)
2336 scrollThread.setMousePosition(evt.getPoint());
2340 * construct a status message showing the range of the selection
2342 StringBuilder status = new StringBuilder(64);
2343 List<SequenceI> seqs = stretchGroup.getSequences();
2344 String name = seqs.get(0).getName();
2345 if (name.length() > 20)
2347 name = name.substring(0, 20);
2349 status.append(name).append(" - ");
2350 name = seqs.get(seqs.size() - 1).getName();
2351 if (name.length() > 20)
2353 name = name.substring(0, 20);
2355 status.append(name).append(" ");
2356 int startRes = stretchGroup.getStartRes();
2357 status.append(" cols ").append(String.valueOf(startRes + 1))
2359 int endRes = stretchGroup.getEndRes();
2360 status.append(String.valueOf(endRes + 1));
2361 status.append(" (").append(String.valueOf(seqs.size())).append(" x ")
2362 .append(String.valueOf(endRes - startRes + 1)).append(")");
2363 ap.alignFrame.setStatus(status.toString());
2367 * Stops the scroll thread if it is running
2369 void stopScrolling()
2371 if (scrollThread != null)
2373 scrollThread.stopScrolling();
2374 scrollThread = null;
2376 mouseDragging = false;
2380 * Starts a thread to scroll the alignment, towards a given mouse position
2381 * outside the panel bounds
2385 void startScrolling(Point mousePos)
2387 if (scrollThread == null)
2389 scrollThread = new ScrollThread();
2392 mouseDragging = true;
2393 scrollThread.setMousePosition(mousePos);
2397 * Performs scrolling of the visible alignment left, right, up or down
2399 class ScrollThread extends Thread
2401 private Point mousePos;
2403 private volatile boolean threadRunning = true;
2408 public ScrollThread()
2410 setName("SeqPanel$ScrollThread");
2415 * Sets the position of the mouse that determines the direction of the
2420 public void setMousePosition(Point p)
2426 * Sets a flag that will cause the thread to exit
2428 public void stopScrolling()
2430 threadRunning = false;
2434 * Scrolls the alignment left or right, and/or up or down, depending on the
2435 * last notified mouse position, until the limit of the alignment is
2436 * reached, or a flag is set to stop the scroll
2441 while (threadRunning && mouseDragging)
2443 if (mousePos != null)
2445 boolean scrolled = false;
2446 ViewportRanges ranges = SeqPanel.this.av.getRanges();
2453 // mouse is above this panel - try scroll up
2454 scrolled = ranges.scrollUp(true);
2456 else if (mousePos.y >= getHeight())
2458 // mouse is below this panel - try scroll down
2459 scrolled = ranges.scrollUp(false);
2463 * scroll left or right
2467 scrolled |= ranges.scrollRight(false);
2469 else if (mousePos.x >= getWidth())
2471 scrolled |= ranges.scrollRight(true);
2476 * we have reached the limit of the visible alignment - quit
2478 threadRunning = false;
2479 SeqPanel.this.ap.repaint();
2486 } catch (Exception ex)
2494 * modify current selection according to a received message.
2497 public void selection(SequenceGroup seqsel, ColumnSelection colsel,
2498 HiddenColumns hidden, SelectionSource source)
2500 // TODO: fix this hack - source of messages is align viewport, but SeqPanel
2501 // handles selection messages...
2502 // TODO: extend config options to allow user to control if selections may be
2503 // shared between viewports.
2504 boolean iSentTheSelection = (av == source
2505 || (source instanceof AlignViewport
2506 && ((AlignmentViewport) source).getSequenceSetId()
2507 .equals(av.getSequenceSetId())));
2509 if (iSentTheSelection)
2511 // respond to our own event by updating dependent dialogs
2512 if (ap.getCalculationDialog() != null)
2514 ap.getCalculationDialog().validateCalcTypes();
2520 // process further ?
2521 if (!av.followSelection)
2527 * Ignore the selection if there is one of our own pending.
2529 if (av.isSelectionGroupChanged(false) || av.isColSelChanged(false))
2535 * Check for selection in a view of which this one is a dna/protein
2538 if (selectionFromTranslation(seqsel, colsel, hidden, source))
2543 // do we want to thread this ? (contention with seqsel and colsel locks, I
2546 * only copy colsel if there is a real intersection between
2547 * sequence selection and this panel's alignment
2549 boolean repaint = false;
2550 boolean copycolsel = false;
2552 SequenceGroup sgroup = null;
2553 if (seqsel != null && seqsel.getSize() > 0)
2555 if (av.getAlignment() == null)
2557 Cache.log.warn("alignviewport av SeqSetId=" + av.getSequenceSetId()
2558 + " ViewId=" + av.getViewId()
2559 + " 's alignment is NULL! returning immediately.");
2562 sgroup = seqsel.intersect(av.getAlignment(),
2563 (av.hasHiddenRows()) ? av.getHiddenRepSequences() : null);
2564 if ((sgroup != null && sgroup.getSize() > 0))
2569 if (sgroup != null && sgroup.getSize() > 0)
2571 av.setSelectionGroup(sgroup);
2575 av.setSelectionGroup(null);
2577 av.isSelectionGroupChanged(true);
2582 // the current selection is unset or from a previous message
2583 // so import the new colsel.
2584 if (colsel == null || colsel.isEmpty())
2586 if (av.getColumnSelection() != null)
2588 av.getColumnSelection().clear();
2594 // TODO: shift colSel according to the intersecting sequences
2595 if (av.getColumnSelection() == null)
2597 av.setColumnSelection(new ColumnSelection(colsel));
2601 av.getColumnSelection().setElementsFrom(colsel,
2602 av.getAlignment().getHiddenColumns());
2605 av.isColSelChanged(true);
2609 if (copycolsel && av.hasHiddenColumns()
2610 && (av.getAlignment().getHiddenColumns() == null))
2612 System.err.println("Bad things");
2614 if (repaint) // always true!
2616 // probably finessing with multiple redraws here
2617 PaintRefresher.Refresh(this, av.getSequenceSetId());
2618 // ap.paintAlignment(false);
2621 // lastly, update dependent dialogs
2622 if (ap.getCalculationDialog() != null)
2624 ap.getCalculationDialog().validateCalcTypes();
2630 * If this panel is a cdna/protein translation view of the selection source,
2631 * tries to map the source selection to a local one, and returns true. Else
2638 protected boolean selectionFromTranslation(SequenceGroup seqsel,
2639 ColumnSelection colsel, HiddenColumns hidden,
2640 SelectionSource source)
2642 if (!(source instanceof AlignViewportI))
2646 final AlignViewportI sourceAv = (AlignViewportI) source;
2647 if (sourceAv.getCodingComplement() != av
2648 && av.getCodingComplement() != sourceAv)
2654 * Map sequence selection
2656 SequenceGroup sg = MappingUtils.mapSequenceGroup(seqsel, sourceAv, av);
2657 av.setSelectionGroup(sg);
2658 av.isSelectionGroupChanged(true);
2661 * Map column selection
2663 // ColumnSelection cs = MappingUtils.mapColumnSelection(colsel, sourceAv,
2665 ColumnSelection cs = new ColumnSelection();
2666 HiddenColumns hs = new HiddenColumns();
2667 MappingUtils.mapColumnSelection(colsel, hidden, sourceAv, av, cs, hs);
2668 av.setColumnSelection(cs);
2669 boolean hiddenChanged = av.getAlignment().setHiddenColumns(hs);
2671 // lastly, update any dependent dialogs
2672 if (ap.getCalculationDialog() != null)
2674 ap.getCalculationDialog().validateCalcTypes();
2678 * repaint alignment, and also Overview or Structure
2679 * if hidden column selection has changed
2681 ap.paintAlignment(hiddenChanged, hiddenChanged);
2688 * @return null or last search results handled by this panel
2690 public SearchResultsI getLastSearchResults()
2692 return lastSearchResults;