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;
258 MousePos findMousePosition(MouseEvent evt)
260 return findMousePosition(evt, false);
264 * Computes the column and sequence row (and possibly annotation row when in
265 * wrapped mode) for the given mouse position
270 MousePos findMousePosition(MouseEvent evt, boolean debug)
272 int col = findColumn(evt, debug);
277 int charHeight = av.getCharHeight();
278 int alignmentHeight = av.getAlignment().getHeight();
281 System.out.println(String.format(
282 "charHeight %d alHeight %d canvasWidth %d canvasHeight %d",
283 charHeight, alignmentHeight, seqCanvas.getWidth(),
284 seqCanvas.getHeight()));
286 if (av.getWrapAlignment())
288 seqCanvas.calculateWrappedGeometry(seqCanvas.getWidth(),
289 seqCanvas.getHeight());
292 * yPos modulo height of repeating width
294 int yOffsetPx = y % seqCanvas.wrappedRepeatHeightPx;
297 * height of sequences plus space / scale above,
298 * plus gap between sequences and annotations
300 int alignmentHeightPixels = seqCanvas.wrappedSpaceAboveAlignment
301 + alignmentHeight * charHeight
302 + SeqCanvas.SEQS_ANNOTATION_GAP;
303 if (yOffsetPx >= alignmentHeightPixels)
306 * mouse is over annotations; find annotation index, also set
307 * last sequence above (for backwards compatible behaviour)
309 AlignmentAnnotation[] anns = av.getAlignment()
310 .getAlignmentAnnotation();
311 int rowOffsetPx = yOffsetPx - alignmentHeightPixels;
312 annIndex = AnnotationPanel.getRowIndex(rowOffsetPx, anns);
313 seqIndex = alignmentHeight - 1;
318 * mouse is over sequence (or the space above sequences)
320 yOffsetPx -= seqCanvas.wrappedSpaceAboveAlignment;
323 seqIndex = Math.min(yOffsetPx / charHeight, alignmentHeight - 1);
329 ViewportRanges ranges = av.getRanges();
330 seqIndex = Math.min((y / charHeight) + ranges.getStartSeq(),
331 alignmentHeight - 1);
332 seqIndex = Math.min(seqIndex, ranges.getEndSeq());
335 return new MousePos(col, seqIndex, annIndex);
338 * Returns the aligned sequence position (base 0) at the mouse position, or
339 * the closest visible one
344 int findColumn(MouseEvent evt)
346 return findColumn(evt, true);
349 int findColumn(MouseEvent evt, boolean debug)
354 final int startRes = av.getRanges().getStartRes();
355 final int charWidth = av.getCharWidth();
357 if (av.getWrapAlignment())
359 int hgap = av.getCharHeight();
360 if (av.getScaleAboveWrapped())
362 hgap += av.getCharHeight();
365 int cHeight = av.getAlignment().getHeight() * av.getCharHeight()
366 + hgap + seqCanvas.getAnnotationHeight();
369 y = Math.max(0, y - hgap);
370 x -= seqCanvas.getLabelWidthWest();
374 String.format("findColumn: x %d labelWest %d charWidth %d ",
375 x, seqCanvas.getLabelWidthWest(), charWidth));
379 // mouse is over left scale
383 int cwidth = seqCanvas.getWrappedCanvasWidth(this.getWidth());
388 if (x >= cwidth * charWidth)
392 System.out.println("findColumn: cwidth = " + cwidth);
394 // mouse is over right scale
398 wrappedBlock = y / cHeight;
399 wrappedBlock += startRes / cwidth;
400 // allow for wrapped view scrolled right (possible from Overview)
401 int startOffset = startRes % cwidth;
402 res = wrappedBlock * cwidth + startOffset
403 + Math.min(cwidth - 1, x / charWidth);
408 * make sure we calculate relative to visible alignment,
409 * rather than right-hand gutter
411 x = Math.min(x, seqCanvas.getX() + seqCanvas.getWidth());
412 res = (x / charWidth) + startRes;
413 res = Math.min(res, av.getRanges().getEndRes());
416 if (av.hasHiddenColumns())
418 res = av.getAlignment().getHiddenColumns()
419 .visibleToAbsoluteColumn(res);
426 * When all of a sequence of edits are complete, put the resulting edit list
427 * on the history stack (undo list), and reset flags for editing in progress.
433 if (editCommand != null && editCommand.getSize() > 0)
435 ap.alignFrame.addHistoryItem(editCommand);
436 av.firePropertyChange("alignment", null,
437 av.getAlignment().getSequences());
442 * Tidy up come what may...
447 groupEditing = false;
456 seqCanvas.cursorY = getKeyboardNo1() - 1;
457 scrollToVisible(true);
460 void setCursorColumn()
462 seqCanvas.cursorX = getKeyboardNo1() - 1;
463 scrollToVisible(true);
466 void setCursorRowAndColumn()
468 if (keyboardNo2 == null)
470 keyboardNo2 = new StringBuffer();
474 seqCanvas.cursorX = getKeyboardNo1() - 1;
475 seqCanvas.cursorY = getKeyboardNo2() - 1;
476 scrollToVisible(true);
480 void setCursorPosition()
482 SequenceI sequence = av.getAlignment().getSequenceAt(seqCanvas.cursorY);
484 seqCanvas.cursorX = sequence.findIndex(getKeyboardNo1()) - 1;
485 scrollToVisible(true);
488 void moveCursor(int dx, int dy)
490 seqCanvas.cursorX += dx;
491 seqCanvas.cursorY += dy;
493 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
495 if (av.hasHiddenColumns() && !hidden.isVisible(seqCanvas.cursorX))
497 int original = seqCanvas.cursorX - dx;
498 int maxWidth = av.getAlignment().getWidth();
500 if (!hidden.isVisible(seqCanvas.cursorX))
502 int visx = hidden.absoluteToVisibleColumn(seqCanvas.cursorX - dx);
503 int[] region = hidden.getRegionWithEdgeAtRes(visx);
505 if (region != null) // just in case
510 seqCanvas.cursorX = region[1] + 1;
515 seqCanvas.cursorX = region[0] - 1;
518 seqCanvas.cursorX = (seqCanvas.cursorX < 0) ? 0 : seqCanvas.cursorX;
521 if (seqCanvas.cursorX >= maxWidth
522 || !hidden.isVisible(seqCanvas.cursorX))
524 seqCanvas.cursorX = original;
528 scrollToVisible(false);
532 * Scroll to make the cursor visible in the viewport.
535 * just jump to the location rather than scrolling
537 void scrollToVisible(boolean jump)
539 if (seqCanvas.cursorX < 0)
541 seqCanvas.cursorX = 0;
543 else if (seqCanvas.cursorX > av.getAlignment().getWidth() - 1)
545 seqCanvas.cursorX = av.getAlignment().getWidth() - 1;
548 if (seqCanvas.cursorY < 0)
550 seqCanvas.cursorY = 0;
552 else if (seqCanvas.cursorY > av.getAlignment().getHeight() - 1)
554 seqCanvas.cursorY = av.getAlignment().getHeight() - 1;
559 boolean repaintNeeded = true;
562 // only need to repaint if the viewport did not move, as otherwise it will
564 repaintNeeded = !av.getRanges().setViewportLocation(seqCanvas.cursorX,
569 if (av.getWrapAlignment())
571 // scrollToWrappedVisible expects x-value to have hidden cols subtracted
572 int x = av.getAlignment().getHiddenColumns()
573 .absoluteToVisibleColumn(seqCanvas.cursorX);
574 av.getRanges().scrollToWrappedVisible(x);
578 av.getRanges().scrollToVisible(seqCanvas.cursorX,
583 if (av.getAlignment().getHiddenColumns().isVisible(seqCanvas.cursorX))
585 setStatusMessage(av.getAlignment().getSequenceAt(seqCanvas.cursorY),
586 seqCanvas.cursorX, seqCanvas.cursorY);
596 void setSelectionAreaAtCursor(boolean topLeft)
598 SequenceI sequence = av.getAlignment().getSequenceAt(seqCanvas.cursorY);
600 if (av.getSelectionGroup() != null)
602 SequenceGroup sg = av.getSelectionGroup();
603 // Find the top and bottom of this group
604 int min = av.getAlignment().getHeight(), max = 0;
605 for (int i = 0; i < sg.getSize(); i++)
607 int index = av.getAlignment().findIndex(sg.getSequenceAt(i));
622 sg.setStartRes(seqCanvas.cursorX);
623 if (sg.getEndRes() < seqCanvas.cursorX)
625 sg.setEndRes(seqCanvas.cursorX);
628 min = seqCanvas.cursorY;
632 sg.setEndRes(seqCanvas.cursorX);
633 if (sg.getStartRes() > seqCanvas.cursorX)
635 sg.setStartRes(seqCanvas.cursorX);
638 max = seqCanvas.cursorY + 1;
643 // Only the user can do this
644 av.setSelectionGroup(null);
648 // Now add any sequences between min and max
649 sg.getSequences(null).clear();
650 for (int i = min; i < max; i++)
652 sg.addSequence(av.getAlignment().getSequenceAt(i), false);
657 if (av.getSelectionGroup() == null)
659 SequenceGroup sg = new SequenceGroup();
660 sg.setStartRes(seqCanvas.cursorX);
661 sg.setEndRes(seqCanvas.cursorX);
662 sg.addSequence(sequence, false);
663 av.setSelectionGroup(sg);
666 ap.paintAlignment(false, false);
670 void insertGapAtCursor(boolean group)
672 groupEditing = group;
673 editStartSeq = seqCanvas.cursorY;
674 editLastRes = seqCanvas.cursorX;
675 editSequence(true, false, seqCanvas.cursorX + getKeyboardNo1());
679 void deleteGapAtCursor(boolean group)
681 groupEditing = group;
682 editStartSeq = seqCanvas.cursorY;
683 editLastRes = seqCanvas.cursorX + getKeyboardNo1();
684 editSequence(false, false, seqCanvas.cursorX);
688 void insertNucAtCursor(boolean group, String nuc)
690 // TODO not called - delete?
691 groupEditing = group;
692 editStartSeq = seqCanvas.cursorY;
693 editLastRes = seqCanvas.cursorX;
694 editSequence(false, true, seqCanvas.cursorX + getKeyboardNo1());
698 void numberPressed(char value)
700 if (keyboardNo1 == null)
702 keyboardNo1 = new StringBuffer();
705 if (keyboardNo2 != null)
707 keyboardNo2.append(value);
711 keyboardNo1.append(value);
719 if (keyboardNo1 != null)
721 int value = Integer.parseInt(keyboardNo1.toString());
725 } catch (Exception x)
736 if (keyboardNo2 != null)
738 int value = Integer.parseInt(keyboardNo2.toString());
742 } catch (Exception x)
756 public void mouseReleased(MouseEvent evt)
758 MousePos pos = findMousePosition(evt);
759 if (pos.isOverAnnotation() || pos.seqIndex == -1 || pos.column == -1)
764 boolean didDrag = mouseDragging; // did we come here after a drag
765 mouseDragging = false;
766 mouseWheelPressed = false;
768 if (evt.isPopupTrigger()) // Windows: mouseReleased
770 showPopupMenu(evt, pos);
781 doMouseReleasedDefineMode(evt, didDrag);
792 public void mousePressed(MouseEvent evt)
794 lastMousePress = evt.getPoint();
795 MousePos pos = findMousePosition(evt);
796 if (pos.isOverAnnotation() || pos.seqIndex == -1 || pos.column == -1)
801 if (SwingUtilities.isMiddleMouseButton(evt))
803 mouseWheelPressed = true;
807 boolean isControlDown = Platform.isControlDown(evt);
808 if (evt.isShiftDown() || isControlDown)
818 doMousePressedDefineMode(evt, pos);
822 int seq = pos.seqIndex;
823 int res = pos.column;
825 if ((seq < av.getAlignment().getHeight())
826 && (res < av.getAlignment().getSequenceAt(seq).getLength()))
843 public void mouseOverSequence(SequenceI sequence, int index, int pos)
845 String tmp = sequence.hashCode() + " " + index + " " + pos;
847 if (lastMessage == null || !lastMessage.equals(tmp))
849 // System.err.println("mouseOver Sequence: "+tmp);
850 ssm.mouseOverSequence(sequence, index, pos, av);
856 * Highlight the mapped region described by the search results object (unless
857 * unchanged). This supports highlight of protein while mousing over linked
858 * cDNA and vice versa. The status bar is also updated to show the location of
859 * the start of the highlighted region.
862 public void highlightSequence(SearchResultsI results)
864 if (results == null || results.equals(lastSearchResults))
868 lastSearchResults = results;
870 boolean wasScrolled = false;
872 if (av.isFollowHighlight())
874 // don't allow highlight of protein/cDNA to also scroll a complementary
875 // panel,as this sets up a feedback loop (scrolling panel 1 causes moused
876 // over residue to change abruptly, causing highlighted residue in panel 2
877 // to change, causing a scroll in panel 1 etc)
878 ap.setToScrollComplementPanel(false);
879 wasScrolled = ap.scrollToPosition(results);
882 seqCanvas.revalidate();
884 ap.setToScrollComplementPanel(true);
887 boolean noFastPaint = wasScrolled && av.getWrapAlignment();
888 if (seqCanvas.highlightSearchResults(results, noFastPaint))
890 setStatusMessage(results);
895 public VamsasSource getVamsasSource()
897 return this.ap == null ? null : this.ap.av;
901 public void updateColours(SequenceI seq, int index)
903 System.out.println("update the seqPanel colours");
908 * Action on mouse movement is to update the status bar to show the current
909 * sequence position, and (if features are shown) to show any features at the
910 * position in a tooltip. Does nothing if the mouse move does not change
916 public void mouseMoved(MouseEvent evt)
920 // This is because MacOSX creates a mouseMoved
921 // If control is down, other platforms will not.
925 final MousePos mousePos = findMousePosition(evt);
926 if (mousePos.equals(lastMousePosition))
929 * just a pixel move without change of 'cell'
933 lastMousePosition = mousePos;
935 if (mousePos.isOverAnnotation())
937 mouseMovedOverAnnotation(mousePos);
940 final int seq = mousePos.seqIndex;
942 final int column = mousePos.column;
943 if (column < 0 || seq < 0 || seq >= av.getAlignment().getHeight())
945 lastMousePosition = null;
946 setToolTipText(null);
948 ap.alignFrame.setStatus("");
952 SequenceI sequence = av.getAlignment().getSequenceAt(seq);
954 if (column >= sequence.getLength())
960 * set status bar message, returning residue position in sequence
962 boolean isGapped = Comparison.isGap(sequence.getCharAt(column));
963 final int pos = setStatusMessage(sequence, column, seq);
964 if (ssm != null && !isGapped)
966 mouseOverSequence(sequence, column, pos);
969 tooltipText.setLength(6); // Cuts the buffer back to <html>
971 SequenceGroup[] groups = av.getAlignment().findAllGroups(sequence);
974 for (int g = 0; g < groups.length; g++)
976 if (groups[g].getStartRes() <= column
977 && groups[g].getEndRes() >= column)
979 if (!groups[g].getName().startsWith("JTreeGroup")
980 && !groups[g].getName().startsWith("JGroup"))
982 tooltipText.append(groups[g].getName());
985 if (groups[g].getDescription() != null)
987 tooltipText.append(": " + groups[g].getDescription());
994 * add any features at the position to the tooltip; if over a gap, only
995 * add features that straddle the gap (pos may be the residue before or
998 if (av.isShowSequenceFeatures())
1000 List<SequenceFeature> features = ap.getFeatureRenderer()
1001 .findFeaturesAtColumn(sequence, column + 1);
1002 seqARep.appendFeatures(tooltipText, pos, features,
1003 this.ap.getSeqPanel().seqCanvas.fr);
1005 if (tooltipText.length() == 6) // <html>
1007 setToolTipText(null);
1012 if (tooltipText.length() > MAX_TOOLTIP_LENGTH) // constant
1014 tooltipText.setLength(MAX_TOOLTIP_LENGTH);
1015 tooltipText.append("...");
1017 String textString = tooltipText.toString();
1018 if (lastTooltip == null || !lastTooltip.equals(textString))
1020 String formattedTooltipText = JvSwingUtils.wrapTooltip(true,
1022 setToolTipText(formattedTooltipText);
1023 lastTooltip = textString;
1029 * When the view is in wrapped mode, and the mouse is over an annotation row,
1030 * shows the corresponding tooltip and status message (if any)
1035 protected void mouseMovedOverAnnotation(MousePos pos)
1037 final int column = pos.column;
1038 final int rowIndex = pos.annotationIndex;
1040 if (column < 0 || !av.getWrapAlignment() || !av.isShowAnnotation()
1045 AlignmentAnnotation[] anns = av.getAlignment().getAlignmentAnnotation();
1047 String tooltip = AnnotationPanel.buildToolTip(anns[rowIndex], column,
1049 setToolTipText(tooltip);
1050 lastTooltip = tooltip;
1052 String msg = AnnotationPanel.getStatusMessage(av.getAlignment(), column,
1054 ap.alignFrame.setStatus(msg);
1057 private Point lastp = null;
1062 * @see javax.swing.JComponent#getToolTipLocation(java.awt.event.MouseEvent)
1065 public Point getToolTipLocation(MouseEvent event)
1067 if (tooltipText == null || tooltipText.length() <= 6)
1073 int x = event.getX();
1075 // switch sides when tooltip is too close to edge
1076 int wdth = (w - x < 200) ? -(w / 2) : 5;
1078 if (!event.isShiftDown() || p == null)
1080 p = new Point(event.getX() + wdth, event.getY() - 20);
1084 * TODO: try to set position so region is not obscured by tooltip
1092 * set when the current UI interaction has resulted in a change that requires
1093 * shading in overviews and structures to be recalculated. this could be
1094 * changed to a something more expressive that indicates what actually has
1095 * changed, so selective redraws can be applied (ie. only structures, only
1098 private boolean updateOverviewAndStructs = false; // TODO: refactor to avcontroller
1101 * set if av.getSelectionGroup() refers to a group that is defined on the
1102 * alignment view, rather than a transient selection
1104 // private boolean editingDefinedGroup = false; // TODO: refactor to
1105 // avcontroller or viewModel
1108 * Sets the status message in alignment panel, showing the sequence number
1109 * (index) and id, and residue and residue position if not at a gap, for the
1110 * given sequence and column position. Returns the residue position returned
1111 * by Sequence.findPosition. Note this may be for the nearest adjacent residue
1112 * if at a gapped position.
1115 * aligned sequence object
1119 * index of sequence in alignment
1120 * @return sequence position of residue at column, or adjacent residue if at a
1123 int setStatusMessage(SequenceI sequence, final int column, int seqIndex)
1125 char sequenceChar = sequence.getCharAt(column);
1126 int pos = sequence.findPosition(column);
1127 setStatusMessage(sequence, seqIndex, sequenceChar, pos);
1133 * Builds the status message for the current cursor location and writes it to
1134 * the status bar, for example
1137 * Sequence 3 ID: FER1_SOLLC
1138 * Sequence 5 ID: FER1_PEA Residue: THR (4)
1139 * Sequence 5 ID: FER1_PEA Residue: B (3)
1140 * Sequence 6 ID: O.niloticus.3 Nucleotide: Uracil (2)
1145 * sequence position in the alignment (1..)
1146 * @param sequenceChar
1147 * the character under the cursor
1149 * the sequence residue position (if not over a gap)
1151 protected void setStatusMessage(SequenceI sequence, int seqIndex,
1152 char sequenceChar, int residuePos)
1154 StringBuilder text = new StringBuilder(32);
1157 * Sequence number (if known), and sequence name.
1159 String seqno = seqIndex == -1 ? "" : " " + (seqIndex + 1);
1160 text.append("Sequence").append(seqno).append(" ID: ")
1161 .append(sequence.getName());
1163 String residue = null;
1166 * Try to translate the display character to residue name (null for gap).
1168 boolean isGapped = Comparison.isGap(sequenceChar);
1172 boolean nucleotide = av.getAlignment().isNucleotide();
1173 String displayChar = String.valueOf(sequenceChar);
1176 residue = ResidueProperties.nucleotideName.get(displayChar);
1180 residue = "X".equalsIgnoreCase(displayChar) ? "X"
1181 : ("*".equals(displayChar) ? "STOP"
1182 : ResidueProperties.aa2Triplet.get(displayChar));
1184 text.append(" ").append(nucleotide ? "Nucleotide" : "Residue")
1185 .append(": ").append(residue == null ? displayChar : residue);
1187 text.append(" (").append(Integer.toString(residuePos)).append(")");
1189 ap.alignFrame.setStatus(text.toString());
1193 * Set the status bar message to highlight the first matched position in
1198 private void setStatusMessage(SearchResultsI results)
1200 AlignmentI al = this.av.getAlignment();
1201 int sequenceIndex = al.findIndex(results);
1202 if (sequenceIndex == -1)
1206 SequenceI ds = al.getSequenceAt(sequenceIndex).getDatasetSequence();
1207 for (SearchResultMatchI m : results.getResults())
1209 SequenceI seq = m.getSequence();
1210 if (seq.getDatasetSequence() != null)
1212 seq = seq.getDatasetSequence();
1217 int start = m.getStart();
1218 setStatusMessage(seq, sequenceIndex, seq.getCharAt(start - 1),
1229 public void mouseDragged(MouseEvent evt)
1231 MousePos pos = findMousePosition(evt);
1232 if (pos.isOverAnnotation() || pos.column == -1)
1237 if (mouseWheelPressed)
1239 boolean inSplitFrame = ap.av.getCodingComplement() != null;
1240 boolean copyChanges = inSplitFrame && av.isProteinFontAsCdna();
1242 int oldWidth = av.getCharWidth();
1244 // Which is bigger, left-right or up-down?
1245 if (Math.abs(evt.getY() - lastMousePress.getY()) > Math
1246 .abs(evt.getX() - lastMousePress.getX()))
1249 * on drag up or down, decrement or increment font size
1251 int fontSize = av.font.getSize();
1252 boolean fontChanged = false;
1254 if (evt.getY() < lastMousePress.getY())
1259 else if (evt.getY() > lastMousePress.getY())
1272 Font newFont = new Font(av.font.getName(), av.font.getStyle(),
1274 av.setFont(newFont, true);
1275 av.setCharWidth(oldWidth);
1279 ap.av.getCodingComplement().setFont(newFont, true);
1280 SplitFrame splitFrame = (SplitFrame) ap.alignFrame
1281 .getSplitViewContainer();
1282 splitFrame.adjustLayout();
1283 splitFrame.repaint();
1290 * on drag left or right, decrement or increment character width
1293 if (evt.getX() < lastMousePress.getX() && av.getCharWidth() > 1)
1295 newWidth = av.getCharWidth() - 1;
1296 av.setCharWidth(newWidth);
1298 else if (evt.getX() > lastMousePress.getX())
1300 newWidth = av.getCharWidth() + 1;
1301 av.setCharWidth(newWidth);
1305 ap.paintAlignment(false, false);
1309 * need to ensure newWidth is set on cdna, regardless of which
1310 * panel the mouse drag happened in; protein will compute its
1311 * character width as 1:1 or 3:1
1313 av.getCodingComplement().setCharWidth(newWidth);
1314 SplitFrame splitFrame = (SplitFrame) ap.alignFrame
1315 .getSplitViewContainer();
1316 splitFrame.adjustLayout();
1317 splitFrame.repaint();
1322 FontMetrics fm = getFontMetrics(av.getFont());
1323 av.validCharWidth = fm.charWidth('M') <= av.getCharWidth();
1325 lastMousePress = evt.getPoint();
1332 dragStretchGroup(evt);
1336 int res = pos.column;
1343 if ((editLastRes == -1) || (editLastRes == res))
1348 if ((res < av.getAlignment().getWidth()) && (res < editLastRes))
1350 // dragLeft, delete gap
1351 editSequence(false, false, res);
1355 editSequence(true, false, res);
1358 mouseDragging = true;
1359 if (scrollThread != null)
1361 scrollThread.setMousePosition(evt.getPoint());
1366 * Edits the sequence to insert or delete one or more gaps, in response to a
1367 * mouse drag or cursor mode command. The number of inserts/deletes may be
1368 * specified with the cursor command, or else depends on the mouse event
1369 * (normally one column, but potentially more for a fast mouse drag).
1371 * Delete gaps is limited to the number of gaps left of the cursor position
1372 * (mouse drag), or at or right of the cursor position (cursor mode).
1374 * In group editing mode (Ctrl or Cmd down), the edit acts on all sequences in
1375 * the current selection group.
1377 * In locked editing mode (with a selection group present), inserts/deletions
1378 * within the selection group are limited to its boundaries (and edits outside
1379 * the group stop at its border).
1382 * true to insert gaps, false to delete gaps
1384 * (unused parameter)
1386 * the column at which to perform the action; the number of columns
1387 * affected depends on <code>this.editLastRes</code> (cursor column
1390 synchronized void editSequence(boolean insertGap, boolean editSeq,
1394 int fixedRight = -1;
1395 boolean fixedColumns = false;
1396 SequenceGroup sg = av.getSelectionGroup();
1398 final SequenceI seq = av.getAlignment().getSequenceAt(editStartSeq);
1400 // No group, but the sequence may represent a group
1401 if (!groupEditing && av.hasHiddenRows())
1403 if (av.isHiddenRepSequence(seq))
1405 sg = av.getRepresentedSequences(seq);
1406 groupEditing = true;
1410 StringBuilder message = new StringBuilder(64); // for status bar
1413 * make a name for the edit action, for
1414 * status bar message and Undo/Redo menu
1416 String label = null;
1419 message.append("Edit group:");
1420 label = MessageManager.getString("action.edit_group");
1424 message.append("Edit sequence: " + seq.getName());
1425 label = seq.getName();
1426 if (label.length() > 10)
1428 label = label.substring(0, 10);
1430 label = MessageManager.formatMessage("label.edit_params",
1436 * initialise the edit command if there is not
1437 * already one being extended
1439 if (editCommand == null)
1441 editCommand = new EditCommand(label);
1446 message.append(" insert ");
1450 message.append(" delete ");
1453 message.append(Math.abs(startres - editLastRes) + " gaps.");
1454 ap.alignFrame.setStatus(message.toString());
1457 * is there a selection group containing the sequence being edited?
1458 * if so the boundary of the group is the limit of the edit
1459 * (but the edit may be inside or outside the selection group)
1461 boolean inSelectionGroup = sg != null
1462 && sg.getSequences(av.getHiddenRepSequences()).contains(seq);
1463 if (groupEditing || inSelectionGroup)
1465 fixedColumns = true;
1467 // sg might be null as the user may only see 1 sequence,
1468 // but the sequence represents a group
1471 if (!av.isHiddenRepSequence(seq))
1476 sg = av.getRepresentedSequences(seq);
1479 fixedLeft = sg.getStartRes();
1480 fixedRight = sg.getEndRes();
1482 if ((startres < fixedLeft && editLastRes >= fixedLeft)
1483 || (startres >= fixedLeft && editLastRes < fixedLeft)
1484 || (startres > fixedRight && editLastRes <= fixedRight)
1485 || (startres <= fixedRight && editLastRes > fixedRight))
1491 if (fixedLeft > startres)
1493 fixedRight = fixedLeft - 1;
1496 else if (fixedRight < startres)
1498 fixedLeft = fixedRight;
1503 if (av.hasHiddenColumns())
1505 fixedColumns = true;
1506 int y1 = av.getAlignment().getHiddenColumns()
1507 .getNextHiddenBoundary(true, startres);
1508 int y2 = av.getAlignment().getHiddenColumns()
1509 .getNextHiddenBoundary(false, startres);
1511 if ((insertGap && startres > y1 && editLastRes < y1)
1512 || (!insertGap && startres < y2 && editLastRes > y2))
1518 // System.out.print(y1+" "+y2+" "+fixedLeft+" "+fixedRight+"~~");
1519 // Selection spans a hidden region
1520 if (fixedLeft < y1 && (fixedRight > y2 || fixedRight == -1))
1528 fixedRight = y2 - 1;
1533 boolean success = doEditSequence(insertGap, editSeq, startres,
1534 fixedRight, fixedColumns, sg);
1537 * report what actually happened (might be less than
1538 * what was requested), by inspecting the edit commands added
1540 String msg = getEditStatusMessage(editCommand);
1541 ap.alignFrame.setStatus(msg == null ? " " : msg);
1547 editLastRes = startres;
1548 seqCanvas.repaint();
1552 * A helper method that performs the requested editing to insert or delete
1553 * gaps (if possible). Answers true if the edit was successful, false if could
1554 * only be performed in part or not at all. Failure may occur in 'locked edit'
1555 * mode, when an insertion requires a matching gapped position (or column) to
1556 * delete, and deletion requires an adjacent gapped position (or column) to
1560 * true if inserting gap(s), false if deleting
1562 * (unused parameter, currently always false)
1564 * the column at which to perform the edit
1566 * fixed right boundary column of a locked edit (within or to the
1567 * left of a selection group)
1568 * @param fixedColumns
1569 * true if this is a locked edit
1571 * the sequence group (if group edit is being performed)
1574 protected boolean doEditSequence(final boolean insertGap,
1575 final boolean editSeq, final int startres, int fixedRight,
1576 final boolean fixedColumns, final SequenceGroup sg)
1578 final SequenceI seq = av.getAlignment().getSequenceAt(editStartSeq);
1579 SequenceI[] seqs = new SequenceI[] { seq };
1583 List<SequenceI> vseqs = sg.getSequences(av.getHiddenRepSequences());
1584 int g, groupSize = vseqs.size();
1585 SequenceI[] groupSeqs = new SequenceI[groupSize];
1586 for (g = 0; g < groupSeqs.length; g++)
1588 groupSeqs[g] = vseqs.get(g);
1594 // If the user has selected the whole sequence, and is dragging to
1595 // the right, we can still extend the alignment and selectionGroup
1596 if (sg.getStartRes() == 0 && sg.getEndRes() == fixedRight
1597 && sg.getEndRes() == av.getAlignment().getWidth() - 1)
1600 av.getAlignment().getWidth() + startres - editLastRes);
1601 fixedRight = sg.getEndRes();
1604 // Is it valid with fixed columns??
1605 // Find the next gap before the end
1606 // of the visible region boundary
1607 boolean blank = false;
1608 for (; fixedRight > editLastRes; fixedRight--)
1612 for (g = 0; g < groupSize; g++)
1614 for (int j = 0; j < startres - editLastRes; j++)
1617 .isGap(groupSeqs[g].getCharAt(fixedRight - j)))
1632 if (sg.getSize() == av.getAlignment().getHeight())
1634 if ((av.hasHiddenColumns()
1635 && startres < av.getAlignment().getHiddenColumns()
1636 .getNextHiddenBoundary(false, startres)))
1641 int alWidth = av.getAlignment().getWidth();
1642 if (av.hasHiddenRows())
1644 int hwidth = av.getAlignment().getHiddenSequences()
1646 if (hwidth > alWidth)
1651 // We can still insert gaps if the selectionGroup
1652 // contains all the sequences
1653 sg.setEndRes(sg.getEndRes() + startres - editLastRes);
1654 fixedRight = alWidth + startres - editLastRes;
1664 else if (!insertGap)
1666 // / Are we able to delete?
1667 // ie are all columns blank?
1669 for (g = 0; g < groupSize; g++)
1671 for (int j = startres; j < editLastRes; j++)
1673 if (groupSeqs[g].getLength() <= j)
1678 if (!Comparison.isGap(groupSeqs[g].getCharAt(j)))
1680 // Not a gap, block edit not valid
1689 // dragging to the right
1690 if (fixedColumns && fixedRight != -1)
1692 for (int j = editLastRes; j < startres; j++)
1694 insertGap(j, groupSeqs, fixedRight);
1699 appendEdit(Action.INSERT_GAP, groupSeqs, startres,
1700 startres - editLastRes, false);
1705 // dragging to the left
1706 if (fixedColumns && fixedRight != -1)
1708 for (int j = editLastRes; j > startres; j--)
1710 deleteChar(startres, groupSeqs, fixedRight);
1715 appendEdit(Action.DELETE_GAP, groupSeqs, startres,
1716 editLastRes - startres, false);
1723 * editing a single sequence
1727 // dragging to the right
1728 if (fixedColumns && fixedRight != -1)
1730 for (int j = editLastRes; j < startres; j++)
1732 if (!insertGap(j, seqs, fixedRight))
1735 * e.g. cursor mode command specified
1736 * more inserts than are possible
1744 appendEdit(Action.INSERT_GAP, seqs, editLastRes,
1745 startres - editLastRes, false);
1752 // dragging to the left
1753 if (fixedColumns && fixedRight != -1)
1755 for (int j = editLastRes; j > startres; j--)
1757 if (!Comparison.isGap(seq.getCharAt(startres)))
1761 deleteChar(startres, seqs, fixedRight);
1766 // could be a keyboard edit trying to delete none gaps
1768 for (int m = startres; m < editLastRes; m++)
1770 if (!Comparison.isGap(seq.getCharAt(m)))
1778 appendEdit(Action.DELETE_GAP, seqs, startres, max, false);
1783 {// insertGap==false AND editSeq==TRUE;
1784 if (fixedColumns && fixedRight != -1)
1786 for (int j = editLastRes; j < startres; j++)
1788 insertGap(j, seqs, fixedRight);
1793 appendEdit(Action.INSERT_NUC, seqs, editLastRes,
1794 startres - editLastRes, false);
1804 * Constructs an informative status bar message while dragging to insert or
1805 * delete gaps. Answers null if inserts and deletes cancel out.
1807 * @param editCommand
1808 * a command containing the list of individual edits
1811 protected static String getEditStatusMessage(EditCommand editCommand)
1813 if (editCommand == null)
1819 * add any inserts, and subtract any deletes,
1820 * not counting those auto-inserted when doing a 'locked edit'
1821 * (so only counting edits 'under the cursor')
1824 for (Edit cmd : editCommand.getEdits())
1826 if (!cmd.isSystemGenerated())
1828 count += cmd.getAction() == Action.INSERT_GAP ? cmd.getNumber()
1836 * inserts and deletes cancel out
1841 String msgKey = count > 1 ? "label.insert_gaps"
1842 : (count == 1 ? "label.insert_gap"
1843 : (count == -1 ? "label.delete_gap"
1844 : "label.delete_gaps"));
1845 count = Math.abs(count);
1847 return MessageManager.formatMessage(msgKey, String.valueOf(count));
1851 * Inserts one gap at column j, deleting the right-most gapped column up to
1852 * (and including) fixedColumn. Returns true if the edit is successful, false
1853 * if no blank column is available to allow the insertion to be balanced by a
1858 * @param fixedColumn
1861 boolean insertGap(int j, SequenceI[] seq, int fixedColumn)
1863 int blankColumn = fixedColumn;
1864 for (int s = 0; s < seq.length; s++)
1866 // Find the next gap before the end of the visible region boundary
1867 // If lastCol > j, theres a boundary after the gap insertion
1869 for (blankColumn = fixedColumn; blankColumn > j; blankColumn--)
1871 if (Comparison.isGap(seq[s].getCharAt(blankColumn)))
1873 // Theres a space, so break and insert the gap
1878 if (blankColumn <= j)
1880 blankColumn = fixedColumn;
1886 appendEdit(Action.DELETE_GAP, seq, blankColumn, 1, true);
1888 appendEdit(Action.INSERT_GAP, seq, j, 1, false);
1894 * Helper method to add and perform one edit action
1900 * @param systemGenerated
1901 * true if the edit is a 'balancing' delete (or insert) to match a
1902 * user's insert (or delete) in a locked editing region
1904 protected void appendEdit(Action action, SequenceI[] seq, int pos,
1905 int count, boolean systemGenerated)
1908 final Edit edit = new EditCommand().new Edit(action, seq, pos, count,
1909 av.getAlignment().getGapCharacter());
1910 edit.setSystemGenerated(systemGenerated);
1912 editCommand.appendEdit(edit, av.getAlignment(), true, null);
1916 * Deletes the character at column j, and inserts a gap at fixedColumn, in
1917 * each of the given sequences. The caller should ensure that all sequences
1918 * are gapped in column j.
1922 * @param fixedColumn
1924 void deleteChar(int j, SequenceI[] seqs, int fixedColumn)
1926 appendEdit(Action.DELETE_GAP, seqs, j, 1, false);
1928 appendEdit(Action.INSERT_GAP, seqs, fixedColumn, 1, true);
1932 * On reentering the panel, stops any scrolling that was started on dragging
1938 public void mouseEntered(MouseEvent e)
1948 * On leaving the panel, if the mouse is being dragged, starts a thread to
1949 * scroll it until the mouse is released (in unwrapped mode only)
1954 public void mouseExited(MouseEvent e)
1956 lastMousePosition = null;
1957 ap.alignFrame.setStatus(" ");
1958 if (av.getWrapAlignment())
1963 if (mouseDragging && scrollThread == null)
1965 scrollThread = new ScrollThread();
1970 * Handler for double-click on a position with one or more sequence features.
1971 * Opens the Amend Features dialog to allow feature details to be amended, or
1972 * the feature deleted.
1975 public void mouseClicked(MouseEvent evt)
1977 SequenceGroup sg = null;
1978 MousePos pos = findMousePosition(evt);
1979 if (pos.isOverAnnotation() || pos.seqIndex == -1 || pos.column == -1)
1984 if (evt.getClickCount() > 1)
1986 sg = av.getSelectionGroup();
1987 if (sg != null && sg.getSize() == 1
1988 && sg.getEndRes() - sg.getStartRes() < 2)
1990 av.setSelectionGroup(null);
1993 int column = pos.column;
1996 * find features at the position (if not gapped), or straddling
1997 * the position (if at a gap)
1999 SequenceI sequence = av.getAlignment().getSequenceAt(pos.seqIndex);
2000 List<SequenceFeature> features = seqCanvas.getFeatureRenderer()
2001 .findFeaturesAtColumn(sequence, column + 1);
2003 if (!features.isEmpty())
2006 * highlight the first feature at the position on the alignment
2008 SearchResultsI highlight = new SearchResults();
2009 highlight.addResult(sequence, features.get(0).getBegin(), features
2011 seqCanvas.highlightSearchResults(highlight, false);
2014 * open the Amend Features dialog; clear highlighting afterwards,
2015 * whether changes were made or not
2017 List<SequenceI> seqs = Collections.singletonList(sequence);
2018 seqCanvas.getFeatureRenderer().amendFeatures(seqs, features, false,
2020 av.setSearchResults(null); // clear highlighting
2021 seqCanvas.repaint(); // draw new/amended features
2027 public void mouseWheelMoved(MouseWheelEvent e)
2030 double wheelRotation = e.getPreciseWheelRotation();
2031 if (wheelRotation > 0)
2033 if (e.isShiftDown())
2035 av.getRanges().scrollRight(true);
2040 av.getRanges().scrollUp(false);
2043 else if (wheelRotation < 0)
2045 if (e.isShiftDown())
2047 av.getRanges().scrollRight(false);
2051 av.getRanges().scrollUp(true);
2056 * update status bar and tooltip for new position
2057 * (need to synthesize a mouse movement to refresh tooltip)
2060 ToolTipManager.sharedInstance().mouseMoved(e);
2069 protected void doMousePressedDefineMode(MouseEvent evt, MousePos pos)
2071 if (pos.isOverAnnotation() || pos.seqIndex == -1 || pos.column == -1)
2076 final int res = pos.column;
2077 final int seq = pos.seqIndex;
2079 updateOverviewAndStructs = false;
2081 startWrapBlock = wrappedBlock;
2083 SequenceI sequence = av.getAlignment().getSequenceAt(seq);
2085 if ((sequence == null) || (res > sequence.getLength()))
2090 stretchGroup = av.getSelectionGroup();
2092 if (stretchGroup == null || !stretchGroup.contains(sequence, res))
2094 stretchGroup = av.getAlignment().findGroup(sequence, res);
2095 if (stretchGroup != null)
2097 // only update the current selection if the popup menu has a group to
2099 av.setSelectionGroup(stretchGroup);
2103 if (evt.isPopupTrigger()) // Mac: mousePressed
2105 showPopupMenu(evt, pos);
2110 * defer right-mouse click handling to mouseReleased on Windows
2111 * (where isPopupTrigger() will answer true)
2112 * NB isRightMouseButton is also true for Cmd-click on Mac
2114 if (SwingUtilities.isRightMouseButton(evt) && !Platform.isAMac())
2121 seqCanvas.cursorX = res;
2122 seqCanvas.cursorY = seq;
2123 seqCanvas.repaint();
2127 if (stretchGroup == null)
2129 createStretchGroup(res, sequence);
2132 if (stretchGroup != null)
2134 stretchGroup.addPropertyChangeListener(seqCanvas);
2137 seqCanvas.repaint();
2140 private void createStretchGroup(int res, SequenceI sequence)
2142 // Only if left mouse button do we want to change group sizes
2143 // define a new group here
2144 SequenceGroup sg = new SequenceGroup();
2145 sg.setStartRes(res);
2147 sg.addSequence(sequence, false);
2148 av.setSelectionGroup(sg);
2151 if (av.getConservationSelected())
2153 SliderPanel.setConservationSlider(ap, av.getResidueShading(),
2157 if (av.getAbovePIDThreshold())
2159 SliderPanel.setPIDSliderSource(ap, av.getResidueShading(),
2162 // TODO: stretchGroup will always be not null. Is this a merge error ?
2163 // or is there a threading issue here?
2164 if ((stretchGroup != null) && (stretchGroup.getEndRes() == res))
2166 // Edit end res position of selected group
2167 changeEndRes = true;
2169 else if ((stretchGroup != null) && (stretchGroup.getStartRes() == res))
2171 // Edit end res position of selected group
2172 changeStartRes = true;
2174 stretchGroup.getWidth();
2179 * Build and show a pop-up menu at the right-click mouse position
2184 void showPopupMenu(MouseEvent evt, MousePos pos)
2186 final int column = pos.column;
2187 final int seq = pos.seqIndex;
2188 SequenceI sequence = av.getAlignment().getSequenceAt(seq);
2189 List<SequenceFeature> features = ap.getFeatureRenderer()
2190 .findFeaturesAtColumn(sequence, column + 1);
2192 PopupMenu pop = new PopupMenu(ap, null, features);
2193 pop.show(this, evt.getX(), evt.getY());
2197 * Update the display after mouse up on a selection or group
2200 * mouse released event details
2202 * true if this event is happening after a mouse drag (rather than a
2205 protected void doMouseReleasedDefineMode(MouseEvent evt,
2208 if (stretchGroup == null)
2213 stretchGroup.removePropertyChangeListener(seqCanvas);
2215 // always do this - annotation has own state
2216 // but defer colourscheme update until hidden sequences are passed in
2217 boolean vischange = stretchGroup.recalcConservation(true);
2218 updateOverviewAndStructs |= vischange && av.isSelectionDefinedGroup()
2220 if (stretchGroup.cs != null)
2224 stretchGroup.cs.alignmentChanged(stretchGroup,
2225 av.getHiddenRepSequences());
2228 ResidueShaderI groupColourScheme = stretchGroup
2229 .getGroupColourScheme();
2230 String name = stretchGroup.getName();
2231 if (stretchGroup.cs.conservationApplied())
2233 SliderPanel.setConservationSlider(ap, groupColourScheme, name);
2235 if (stretchGroup.cs.getThreshold() > 0)
2237 SliderPanel.setPIDSliderSource(ap, groupColourScheme, name);
2240 PaintRefresher.Refresh(this, av.getSequenceSetId());
2241 // TODO: structure colours only need updating if stretchGroup used to or now
2242 // does contain sequences with structure views
2243 ap.paintAlignment(updateOverviewAndStructs, updateOverviewAndStructs);
2244 updateOverviewAndStructs = false;
2245 changeEndRes = false;
2246 changeStartRes = false;
2247 stretchGroup = null;
2252 * Resizes the borders of a selection group depending on the direction of
2257 protected void dragStretchGroup(MouseEvent evt)
2259 if (stretchGroup == null)
2264 MousePos pos = findMousePosition(evt);
2265 if (pos.isOverAnnotation() || pos.column == -1 || pos.seqIndex == -1)
2270 int res = pos.column;
2271 int y = pos.seqIndex;
2273 if (wrappedBlock != startWrapBlock)
2278 res = Math.min(res, av.getAlignment().getWidth()-1);
2280 if (stretchGroup.getEndRes() == res)
2282 // Edit end res position of selected group
2283 changeEndRes = true;
2285 else if (stretchGroup.getStartRes() == res)
2287 // Edit start res position of selected group
2288 changeStartRes = true;
2291 if (res < av.getRanges().getStartRes())
2293 res = av.getRanges().getStartRes();
2298 if (res > (stretchGroup.getStartRes() - 1))
2300 stretchGroup.setEndRes(res);
2301 updateOverviewAndStructs |= av.isSelectionDefinedGroup();
2304 else if (changeStartRes)
2306 if (res < (stretchGroup.getEndRes() + 1))
2308 stretchGroup.setStartRes(res);
2309 updateOverviewAndStructs |= av.isSelectionDefinedGroup();
2313 int dragDirection = 0;
2319 else if (y < oldSeq)
2324 while ((y != oldSeq) && (oldSeq > -1)
2325 && (y < av.getAlignment().getHeight()))
2327 // This routine ensures we don't skip any sequences, as the
2328 // selection is quite slow.
2329 Sequence seq = (Sequence) av.getAlignment().getSequenceAt(oldSeq);
2331 oldSeq += dragDirection;
2338 Sequence nextSeq = (Sequence) av.getAlignment().getSequenceAt(oldSeq);
2340 if (stretchGroup.getSequences(null).contains(nextSeq))
2342 stretchGroup.deleteSequence(seq, false);
2343 updateOverviewAndStructs |= av.isSelectionDefinedGroup();
2349 stretchGroup.addSequence(seq, false);
2352 stretchGroup.addSequence(nextSeq, false);
2353 updateOverviewAndStructs |= av.isSelectionDefinedGroup();
2362 mouseDragging = true;
2364 if (scrollThread != null)
2366 scrollThread.setMousePosition(evt.getPoint());
2370 * construct a status message showing the range of the selection
2372 StringBuilder status = new StringBuilder(64);
2373 List<SequenceI> seqs = stretchGroup.getSequences();
2374 String name = seqs.get(0).getName();
2375 if (name.length() > 20)
2377 name = name.substring(0, 20);
2379 status.append(name).append(" - ");
2380 name = seqs.get(seqs.size() - 1).getName();
2381 if (name.length() > 20)
2383 name = name.substring(0, 20);
2385 status.append(name).append(" ");
2386 int startRes = stretchGroup.getStartRes();
2387 status.append(" cols ").append(String.valueOf(startRes + 1))
2389 int endRes = stretchGroup.getEndRes();
2390 status.append(String.valueOf(endRes + 1));
2391 status.append(" (").append(String.valueOf(seqs.size())).append(" x ")
2392 .append(String.valueOf(endRes - startRes + 1)).append(")");
2393 ap.alignFrame.setStatus(status.toString());
2397 * Stops the scroll thread if it is running
2399 void stopScrolling()
2401 if (scrollThread != null)
2403 scrollThread.stopScrolling();
2404 scrollThread = null;
2406 mouseDragging = false;
2410 * Starts a thread to scroll the alignment, towards a given mouse position
2411 * outside the panel bounds
2415 void startScrolling(Point mousePos)
2417 if (scrollThread == null)
2419 scrollThread = new ScrollThread();
2422 mouseDragging = true;
2423 scrollThread.setMousePosition(mousePos);
2427 * Performs scrolling of the visible alignment left, right, up or down
2429 class ScrollThread extends Thread
2431 private Point mousePos;
2433 private volatile boolean threadRunning = true;
2438 public ScrollThread()
2440 setName("SeqPanel$ScrollThread");
2445 * Sets the position of the mouse that determines the direction of the
2450 public void setMousePosition(Point p)
2456 * Sets a flag that will cause the thread to exit
2458 public void stopScrolling()
2460 threadRunning = false;
2464 * Scrolls the alignment left or right, and/or up or down, depending on the
2465 * last notified mouse position, until the limit of the alignment is
2466 * reached, or a flag is set to stop the scroll
2471 while (threadRunning && mouseDragging)
2473 if (mousePos != null)
2475 boolean scrolled = false;
2476 ViewportRanges ranges = SeqPanel.this.av.getRanges();
2483 // mouse is above this panel - try scroll up
2484 scrolled = ranges.scrollUp(true);
2486 else if (mousePos.y >= getHeight())
2488 // mouse is below this panel - try scroll down
2489 scrolled = ranges.scrollUp(false);
2493 * scroll left or right
2497 scrolled |= ranges.scrollRight(false);
2499 else if (mousePos.x >= getWidth())
2501 scrolled |= ranges.scrollRight(true);
2506 * we have reached the limit of the visible alignment - quit
2508 threadRunning = false;
2509 SeqPanel.this.ap.repaint();
2516 } catch (Exception ex)
2524 * modify current selection according to a received message.
2527 public void selection(SequenceGroup seqsel, ColumnSelection colsel,
2528 HiddenColumns hidden, SelectionSource source)
2530 // TODO: fix this hack - source of messages is align viewport, but SeqPanel
2531 // handles selection messages...
2532 // TODO: extend config options to allow user to control if selections may be
2533 // shared between viewports.
2534 boolean iSentTheSelection = (av == source
2535 || (source instanceof AlignViewport
2536 && ((AlignmentViewport) source).getSequenceSetId()
2537 .equals(av.getSequenceSetId())));
2539 if (iSentTheSelection)
2541 // respond to our own event by updating dependent dialogs
2542 if (ap.getCalculationDialog() != null)
2544 ap.getCalculationDialog().validateCalcTypes();
2550 // process further ?
2551 if (!av.followSelection)
2557 * Ignore the selection if there is one of our own pending.
2559 if (av.isSelectionGroupChanged(false) || av.isColSelChanged(false))
2565 * Check for selection in a view of which this one is a dna/protein
2568 if (selectionFromTranslation(seqsel, colsel, hidden, source))
2573 // do we want to thread this ? (contention with seqsel and colsel locks, I
2576 * only copy colsel if there is a real intersection between
2577 * sequence selection and this panel's alignment
2579 boolean repaint = false;
2580 boolean copycolsel = false;
2582 SequenceGroup sgroup = null;
2583 if (seqsel != null && seqsel.getSize() > 0)
2585 if (av.getAlignment() == null)
2587 Cache.log.warn("alignviewport av SeqSetId=" + av.getSequenceSetId()
2588 + " ViewId=" + av.getViewId()
2589 + " 's alignment is NULL! returning immediately.");
2592 sgroup = seqsel.intersect(av.getAlignment(),
2593 (av.hasHiddenRows()) ? av.getHiddenRepSequences() : null);
2594 if ((sgroup != null && sgroup.getSize() > 0))
2599 if (sgroup != null && sgroup.getSize() > 0)
2601 av.setSelectionGroup(sgroup);
2605 av.setSelectionGroup(null);
2607 av.isSelectionGroupChanged(true);
2612 // the current selection is unset or from a previous message
2613 // so import the new colsel.
2614 if (colsel == null || colsel.isEmpty())
2616 if (av.getColumnSelection() != null)
2618 av.getColumnSelection().clear();
2624 // TODO: shift colSel according to the intersecting sequences
2625 if (av.getColumnSelection() == null)
2627 av.setColumnSelection(new ColumnSelection(colsel));
2631 av.getColumnSelection().setElementsFrom(colsel,
2632 av.getAlignment().getHiddenColumns());
2635 av.isColSelChanged(true);
2639 if (copycolsel && av.hasHiddenColumns()
2640 && (av.getAlignment().getHiddenColumns() == null))
2642 System.err.println("Bad things");
2644 if (repaint) // always true!
2646 // probably finessing with multiple redraws here
2647 PaintRefresher.Refresh(this, av.getSequenceSetId());
2648 // ap.paintAlignment(false);
2651 // lastly, update dependent dialogs
2652 if (ap.getCalculationDialog() != null)
2654 ap.getCalculationDialog().validateCalcTypes();
2660 * If this panel is a cdna/protein translation view of the selection source,
2661 * tries to map the source selection to a local one, and returns true. Else
2668 protected boolean selectionFromTranslation(SequenceGroup seqsel,
2669 ColumnSelection colsel, HiddenColumns hidden,
2670 SelectionSource source)
2672 if (!(source instanceof AlignViewportI))
2676 final AlignViewportI sourceAv = (AlignViewportI) source;
2677 if (sourceAv.getCodingComplement() != av
2678 && av.getCodingComplement() != sourceAv)
2684 * Map sequence selection
2686 SequenceGroup sg = MappingUtils.mapSequenceGroup(seqsel, sourceAv, av);
2687 av.setSelectionGroup(sg);
2688 av.isSelectionGroupChanged(true);
2691 * Map column selection
2693 // ColumnSelection cs = MappingUtils.mapColumnSelection(colsel, sourceAv,
2695 ColumnSelection cs = new ColumnSelection();
2696 HiddenColumns hs = new HiddenColumns();
2697 MappingUtils.mapColumnSelection(colsel, hidden, sourceAv, av, cs, hs);
2698 av.setColumnSelection(cs);
2699 boolean hiddenChanged = av.getAlignment().setHiddenColumns(hs);
2701 // lastly, update any dependent dialogs
2702 if (ap.getCalculationDialog() != null)
2704 ap.getCalculationDialog().validateCalcTypes();
2708 * repaint alignment, and also Overview or Structure
2709 * if hidden column selection has changed
2711 ap.paintAlignment(hiddenChanged, hiddenChanged);
2718 * @return null or last search results handled by this panel
2720 public SearchResultsI getLastSearchResults()
2722 return lastSearchResults;