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, null);
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, String 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 "%s: charHeight %d alHeight %d canvasWidth %d canvasHeight %d",
283 debug, 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, null);
349 int findColumn(MouseEvent evt, String 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("%s: %s %d x %d labelWest %d charWidth %d ",
375 debug, Thread.currentThread().getName(),
376 System.currentTimeMillis(), x,
377 seqCanvas.getLabelWidthWest(), charWidth));
381 // mouse is over left scale
385 int cwidth = seqCanvas.getWrappedCanvasWidth(this.getWidth());
390 if (x >= cwidth * charWidth)
394 System.out.println(debug + ": cwidth = " + cwidth);
396 // mouse is over right scale
400 wrappedBlock = y / cHeight;
401 wrappedBlock += startRes / cwidth;
402 // allow for wrapped view scrolled right (possible from Overview)
403 int startOffset = startRes % cwidth;
404 res = wrappedBlock * cwidth + startOffset
405 + Math.min(cwidth - 1, x / charWidth);
410 * make sure we calculate relative to visible alignment,
411 * rather than right-hand gutter
413 x = Math.min(x, seqCanvas.getX() + seqCanvas.getWidth());
414 res = (x / charWidth) + startRes;
415 res = Math.min(res, av.getRanges().getEndRes());
418 if (av.hasHiddenColumns())
420 res = av.getAlignment().getHiddenColumns()
421 .visibleToAbsoluteColumn(res);
428 * When all of a sequence of edits are complete, put the resulting edit list
429 * on the history stack (undo list), and reset flags for editing in progress.
435 if (editCommand != null && editCommand.getSize() > 0)
437 ap.alignFrame.addHistoryItem(editCommand);
438 av.firePropertyChange("alignment", null,
439 av.getAlignment().getSequences());
444 * Tidy up come what may...
449 groupEditing = false;
458 seqCanvas.cursorY = getKeyboardNo1() - 1;
459 scrollToVisible(true);
462 void setCursorColumn()
464 seqCanvas.cursorX = getKeyboardNo1() - 1;
465 scrollToVisible(true);
468 void setCursorRowAndColumn()
470 if (keyboardNo2 == null)
472 keyboardNo2 = new StringBuffer();
476 seqCanvas.cursorX = getKeyboardNo1() - 1;
477 seqCanvas.cursorY = getKeyboardNo2() - 1;
478 scrollToVisible(true);
482 void setCursorPosition()
484 SequenceI sequence = av.getAlignment().getSequenceAt(seqCanvas.cursorY);
486 seqCanvas.cursorX = sequence.findIndex(getKeyboardNo1()) - 1;
487 scrollToVisible(true);
490 void moveCursor(int dx, int dy)
492 seqCanvas.cursorX += dx;
493 seqCanvas.cursorY += dy;
495 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
497 if (av.hasHiddenColumns() && !hidden.isVisible(seqCanvas.cursorX))
499 int original = seqCanvas.cursorX - dx;
500 int maxWidth = av.getAlignment().getWidth();
502 if (!hidden.isVisible(seqCanvas.cursorX))
504 int visx = hidden.absoluteToVisibleColumn(seqCanvas.cursorX - dx);
505 int[] region = hidden.getRegionWithEdgeAtRes(visx);
507 if (region != null) // just in case
512 seqCanvas.cursorX = region[1] + 1;
517 seqCanvas.cursorX = region[0] - 1;
520 seqCanvas.cursorX = (seqCanvas.cursorX < 0) ? 0 : seqCanvas.cursorX;
523 if (seqCanvas.cursorX >= maxWidth
524 || !hidden.isVisible(seqCanvas.cursorX))
526 seqCanvas.cursorX = original;
530 scrollToVisible(false);
534 * Scroll to make the cursor visible in the viewport.
537 * just jump to the location rather than scrolling
539 void scrollToVisible(boolean jump)
541 if (seqCanvas.cursorX < 0)
543 seqCanvas.cursorX = 0;
545 else if (seqCanvas.cursorX > av.getAlignment().getWidth() - 1)
547 seqCanvas.cursorX = av.getAlignment().getWidth() - 1;
550 if (seqCanvas.cursorY < 0)
552 seqCanvas.cursorY = 0;
554 else if (seqCanvas.cursorY > av.getAlignment().getHeight() - 1)
556 seqCanvas.cursorY = av.getAlignment().getHeight() - 1;
561 boolean repaintNeeded = true;
564 // only need to repaint if the viewport did not move, as otherwise it will
566 repaintNeeded = !av.getRanges().setViewportLocation(seqCanvas.cursorX,
571 if (av.getWrapAlignment())
573 // scrollToWrappedVisible expects x-value to have hidden cols subtracted
574 int x = av.getAlignment().getHiddenColumns()
575 .absoluteToVisibleColumn(seqCanvas.cursorX);
576 av.getRanges().scrollToWrappedVisible(x);
580 av.getRanges().scrollToVisible(seqCanvas.cursorX,
585 if (av.getAlignment().getHiddenColumns().isVisible(seqCanvas.cursorX))
587 setStatusMessage(av.getAlignment().getSequenceAt(seqCanvas.cursorY),
588 seqCanvas.cursorX, seqCanvas.cursorY);
598 void setSelectionAreaAtCursor(boolean topLeft)
600 SequenceI sequence = av.getAlignment().getSequenceAt(seqCanvas.cursorY);
602 if (av.getSelectionGroup() != null)
604 SequenceGroup sg = av.getSelectionGroup();
605 // Find the top and bottom of this group
606 int min = av.getAlignment().getHeight(), max = 0;
607 for (int i = 0; i < sg.getSize(); i++)
609 int index = av.getAlignment().findIndex(sg.getSequenceAt(i));
624 sg.setStartRes(seqCanvas.cursorX);
625 if (sg.getEndRes() < seqCanvas.cursorX)
627 sg.setEndRes(seqCanvas.cursorX);
630 min = seqCanvas.cursorY;
634 sg.setEndRes(seqCanvas.cursorX);
635 if (sg.getStartRes() > seqCanvas.cursorX)
637 sg.setStartRes(seqCanvas.cursorX);
640 max = seqCanvas.cursorY + 1;
645 // Only the user can do this
646 av.setSelectionGroup(null);
650 // Now add any sequences between min and max
651 sg.getSequences(null).clear();
652 for (int i = min; i < max; i++)
654 sg.addSequence(av.getAlignment().getSequenceAt(i), false);
659 if (av.getSelectionGroup() == null)
661 SequenceGroup sg = new SequenceGroup();
662 sg.setStartRes(seqCanvas.cursorX);
663 sg.setEndRes(seqCanvas.cursorX);
664 sg.addSequence(sequence, false);
665 av.setSelectionGroup(sg);
668 ap.paintAlignment(false, false);
672 void insertGapAtCursor(boolean group)
674 groupEditing = group;
675 editStartSeq = seqCanvas.cursorY;
676 editLastRes = seqCanvas.cursorX;
677 editSequence(true, false, seqCanvas.cursorX + getKeyboardNo1());
681 void deleteGapAtCursor(boolean group)
683 groupEditing = group;
684 editStartSeq = seqCanvas.cursorY;
685 editLastRes = seqCanvas.cursorX + getKeyboardNo1();
686 editSequence(false, false, seqCanvas.cursorX);
690 void insertNucAtCursor(boolean group, String nuc)
692 // TODO not called - delete?
693 groupEditing = group;
694 editStartSeq = seqCanvas.cursorY;
695 editLastRes = seqCanvas.cursorX;
696 editSequence(false, true, seqCanvas.cursorX + getKeyboardNo1());
700 void numberPressed(char value)
702 if (keyboardNo1 == null)
704 keyboardNo1 = new StringBuffer();
707 if (keyboardNo2 != null)
709 keyboardNo2.append(value);
713 keyboardNo1.append(value);
721 if (keyboardNo1 != null)
723 int value = Integer.parseInt(keyboardNo1.toString());
727 } catch (Exception x)
738 if (keyboardNo2 != null)
740 int value = Integer.parseInt(keyboardNo2.toString());
744 } catch (Exception x)
758 public void mouseReleased(MouseEvent evt)
760 MousePos pos = findMousePosition(evt);
761 if (pos.isOverAnnotation() || pos.seqIndex == -1 || pos.column == -1)
766 boolean didDrag = mouseDragging; // did we come here after a drag
767 mouseDragging = false;
768 mouseWheelPressed = false;
770 if (evt.isPopupTrigger()) // Windows: mouseReleased
772 showPopupMenu(evt, pos);
783 doMouseReleasedDefineMode(evt, didDrag);
794 public void mousePressed(MouseEvent evt)
796 lastMousePress = evt.getPoint();
797 MousePos pos = findMousePosition(evt);
798 if (pos.isOverAnnotation() || pos.seqIndex == -1 || pos.column == -1)
803 if (SwingUtilities.isMiddleMouseButton(evt))
805 mouseWheelPressed = true;
809 boolean isControlDown = Platform.isControlDown(evt);
810 if (evt.isShiftDown() || isControlDown)
820 doMousePressedDefineMode(evt, pos);
824 int seq = pos.seqIndex;
825 int res = pos.column;
827 if ((seq < av.getAlignment().getHeight())
828 && (res < av.getAlignment().getSequenceAt(seq).getLength()))
845 public void mouseOverSequence(SequenceI sequence, int index, int pos)
847 String tmp = sequence.hashCode() + " " + index + " " + pos;
849 if (lastMessage == null || !lastMessage.equals(tmp))
851 // System.err.println("mouseOver Sequence: "+tmp);
852 ssm.mouseOverSequence(sequence, index, pos, av);
858 * Highlight the mapped region described by the search results object (unless
859 * unchanged). This supports highlight of protein while mousing over linked
860 * cDNA and vice versa. The status bar is also updated to show the location of
861 * the start of the highlighted region.
864 public void highlightSequence(SearchResultsI results)
866 if (results == null || results.equals(lastSearchResults))
870 lastSearchResults = results;
872 boolean wasScrolled = false;
874 if (av.isFollowHighlight())
876 // don't allow highlight of protein/cDNA to also scroll a complementary
877 // panel,as this sets up a feedback loop (scrolling panel 1 causes moused
878 // over residue to change abruptly, causing highlighted residue in panel 2
879 // to change, causing a scroll in panel 1 etc)
880 ap.setToScrollComplementPanel(false);
881 wasScrolled = ap.scrollToPosition(results);
884 seqCanvas.revalidate();
886 ap.setToScrollComplementPanel(true);
889 boolean noFastPaint = wasScrolled && av.getWrapAlignment();
890 if (seqCanvas.highlightSearchResults(results, noFastPaint))
892 setStatusMessage(results);
897 public VamsasSource getVamsasSource()
899 return this.ap == null ? null : this.ap.av;
903 public void updateColours(SequenceI seq, int index)
905 System.out.println("update the seqPanel colours");
910 * Action on mouse movement is to update the status bar to show the current
911 * sequence position, and (if features are shown) to show any features at the
912 * position in a tooltip. Does nothing if the mouse move does not change
918 public void mouseMoved(MouseEvent evt)
922 // This is because MacOSX creates a mouseMoved
923 // If control is down, other platforms will not.
927 final MousePos mousePos = findMousePosition(evt);
928 if (mousePos.equals(lastMousePosition))
931 * just a pixel move without change of 'cell'
935 lastMousePosition = mousePos;
937 if (mousePos.isOverAnnotation())
939 mouseMovedOverAnnotation(mousePos);
942 final int seq = mousePos.seqIndex;
944 final int column = mousePos.column;
945 if (column < 0 || seq < 0 || seq >= av.getAlignment().getHeight())
947 lastMousePosition = null;
948 setToolTipText(null);
950 ap.alignFrame.setStatus("");
954 SequenceI sequence = av.getAlignment().getSequenceAt(seq);
956 if (column >= sequence.getLength())
962 * set status bar message, returning residue position in sequence
964 boolean isGapped = Comparison.isGap(sequence.getCharAt(column));
965 final int pos = setStatusMessage(sequence, column, seq);
966 if (ssm != null && !isGapped)
968 mouseOverSequence(sequence, column, pos);
971 tooltipText.setLength(6); // Cuts the buffer back to <html>
973 SequenceGroup[] groups = av.getAlignment().findAllGroups(sequence);
976 for (int g = 0; g < groups.length; g++)
978 if (groups[g].getStartRes() <= column
979 && groups[g].getEndRes() >= column)
981 if (!groups[g].getName().startsWith("JTreeGroup")
982 && !groups[g].getName().startsWith("JGroup"))
984 tooltipText.append(groups[g].getName());
987 if (groups[g].getDescription() != null)
989 tooltipText.append(": " + groups[g].getDescription());
996 * add any features at the position to the tooltip; if over a gap, only
997 * add features that straddle the gap (pos may be the residue before or
1000 if (av.isShowSequenceFeatures())
1002 List<SequenceFeature> features = ap.getFeatureRenderer()
1003 .findFeaturesAtColumn(sequence, column + 1);
1004 seqARep.appendFeatures(tooltipText, pos, features,
1005 this.ap.getSeqPanel().seqCanvas.fr);
1007 if (tooltipText.length() == 6) // <html>
1009 setToolTipText(null);
1014 if (tooltipText.length() > MAX_TOOLTIP_LENGTH) // constant
1016 tooltipText.setLength(MAX_TOOLTIP_LENGTH);
1017 tooltipText.append("...");
1019 String textString = tooltipText.toString();
1020 if (lastTooltip == null || !lastTooltip.equals(textString))
1022 String formattedTooltipText = JvSwingUtils.wrapTooltip(true,
1024 setToolTipText(formattedTooltipText);
1025 lastTooltip = textString;
1031 * When the view is in wrapped mode, and the mouse is over an annotation row,
1032 * shows the corresponding tooltip and status message (if any)
1037 protected void mouseMovedOverAnnotation(MousePos pos)
1039 final int column = pos.column;
1040 final int rowIndex = pos.annotationIndex;
1042 if (column < 0 || !av.getWrapAlignment() || !av.isShowAnnotation()
1047 AlignmentAnnotation[] anns = av.getAlignment().getAlignmentAnnotation();
1049 String tooltip = AnnotationPanel.buildToolTip(anns[rowIndex], column,
1051 setToolTipText(tooltip);
1052 lastTooltip = tooltip;
1054 String msg = AnnotationPanel.getStatusMessage(av.getAlignment(), column,
1056 ap.alignFrame.setStatus(msg);
1059 private Point lastp = null;
1064 * @see javax.swing.JComponent#getToolTipLocation(java.awt.event.MouseEvent)
1067 public Point getToolTipLocation(MouseEvent event)
1069 if (tooltipText == null || tooltipText.length() <= 6)
1075 int x = event.getX();
1077 // switch sides when tooltip is too close to edge
1078 int wdth = (w - x < 200) ? -(w / 2) : 5;
1080 if (!event.isShiftDown() || p == null)
1082 p = new Point(event.getX() + wdth, event.getY() - 20);
1086 * TODO: try to set position so region is not obscured by tooltip
1094 * set when the current UI interaction has resulted in a change that requires
1095 * shading in overviews and structures to be recalculated. this could be
1096 * changed to a something more expressive that indicates what actually has
1097 * changed, so selective redraws can be applied (ie. only structures, only
1100 private boolean updateOverviewAndStructs = false; // TODO: refactor to avcontroller
1103 * set if av.getSelectionGroup() refers to a group that is defined on the
1104 * alignment view, rather than a transient selection
1106 // private boolean editingDefinedGroup = false; // TODO: refactor to
1107 // avcontroller or viewModel
1110 * Sets the status message in alignment panel, showing the sequence number
1111 * (index) and id, and residue and residue position if not at a gap, for the
1112 * given sequence and column position. Returns the residue position returned
1113 * by Sequence.findPosition. Note this may be for the nearest adjacent residue
1114 * if at a gapped position.
1117 * aligned sequence object
1121 * index of sequence in alignment
1122 * @return sequence position of residue at column, or adjacent residue if at a
1125 int setStatusMessage(SequenceI sequence, final int column, int seqIndex)
1127 char sequenceChar = sequence.getCharAt(column);
1128 int pos = sequence.findPosition(column);
1129 setStatusMessage(sequence, seqIndex, sequenceChar, pos);
1135 * Builds the status message for the current cursor location and writes it to
1136 * the status bar, for example
1139 * Sequence 3 ID: FER1_SOLLC
1140 * Sequence 5 ID: FER1_PEA Residue: THR (4)
1141 * Sequence 5 ID: FER1_PEA Residue: B (3)
1142 * Sequence 6 ID: O.niloticus.3 Nucleotide: Uracil (2)
1147 * sequence position in the alignment (1..)
1148 * @param sequenceChar
1149 * the character under the cursor
1151 * the sequence residue position (if not over a gap)
1153 protected void setStatusMessage(SequenceI sequence, int seqIndex,
1154 char sequenceChar, int residuePos)
1156 StringBuilder text = new StringBuilder(32);
1159 * Sequence number (if known), and sequence name.
1161 String seqno = seqIndex == -1 ? "" : " " + (seqIndex + 1);
1162 text.append("Sequence").append(seqno).append(" ID: ")
1163 .append(sequence.getName());
1165 String residue = null;
1168 * Try to translate the display character to residue name (null for gap).
1170 boolean isGapped = Comparison.isGap(sequenceChar);
1174 boolean nucleotide = av.getAlignment().isNucleotide();
1175 String displayChar = String.valueOf(sequenceChar);
1178 residue = ResidueProperties.nucleotideName.get(displayChar);
1182 residue = "X".equalsIgnoreCase(displayChar) ? "X"
1183 : ("*".equals(displayChar) ? "STOP"
1184 : ResidueProperties.aa2Triplet.get(displayChar));
1186 text.append(" ").append(nucleotide ? "Nucleotide" : "Residue")
1187 .append(": ").append(residue == null ? displayChar : residue);
1189 text.append(" (").append(Integer.toString(residuePos)).append(")");
1191 ap.alignFrame.setStatus(text.toString());
1195 * Set the status bar message to highlight the first matched position in
1200 private void setStatusMessage(SearchResultsI results)
1202 AlignmentI al = this.av.getAlignment();
1203 int sequenceIndex = al.findIndex(results);
1204 if (sequenceIndex == -1)
1208 SequenceI ds = al.getSequenceAt(sequenceIndex).getDatasetSequence();
1209 for (SearchResultMatchI m : results.getResults())
1211 SequenceI seq = m.getSequence();
1212 if (seq.getDatasetSequence() != null)
1214 seq = seq.getDatasetSequence();
1219 int start = m.getStart();
1220 setStatusMessage(seq, sequenceIndex, seq.getCharAt(start - 1),
1231 public void mouseDragged(MouseEvent evt)
1233 MousePos pos = findMousePosition(evt);
1234 if (pos.isOverAnnotation() || pos.column == -1)
1239 if (mouseWheelPressed)
1241 boolean inSplitFrame = ap.av.getCodingComplement() != null;
1242 boolean copyChanges = inSplitFrame && av.isProteinFontAsCdna();
1244 int oldWidth = av.getCharWidth();
1246 // Which is bigger, left-right or up-down?
1247 if (Math.abs(evt.getY() - lastMousePress.getY()) > Math
1248 .abs(evt.getX() - lastMousePress.getX()))
1251 * on drag up or down, decrement or increment font size
1253 int fontSize = av.font.getSize();
1254 boolean fontChanged = false;
1256 if (evt.getY() < lastMousePress.getY())
1261 else if (evt.getY() > lastMousePress.getY())
1274 Font newFont = new Font(av.font.getName(), av.font.getStyle(),
1276 av.setFont(newFont, true);
1277 av.setCharWidth(oldWidth);
1281 ap.av.getCodingComplement().setFont(newFont, true);
1282 SplitFrame splitFrame = (SplitFrame) ap.alignFrame
1283 .getSplitViewContainer();
1284 splitFrame.adjustLayout();
1285 splitFrame.repaint();
1292 * on drag left or right, decrement or increment character width
1295 if (evt.getX() < lastMousePress.getX() && av.getCharWidth() > 1)
1297 newWidth = av.getCharWidth() - 1;
1298 av.setCharWidth(newWidth);
1300 else if (evt.getX() > lastMousePress.getX())
1302 newWidth = av.getCharWidth() + 1;
1303 av.setCharWidth(newWidth);
1307 ap.paintAlignment(false, false);
1311 * need to ensure newWidth is set on cdna, regardless of which
1312 * panel the mouse drag happened in; protein will compute its
1313 * character width as 1:1 or 3:1
1315 av.getCodingComplement().setCharWidth(newWidth);
1316 SplitFrame splitFrame = (SplitFrame) ap.alignFrame
1317 .getSplitViewContainer();
1318 splitFrame.adjustLayout();
1319 splitFrame.repaint();
1324 FontMetrics fm = getFontMetrics(av.getFont());
1325 av.validCharWidth = fm.charWidth('M') <= av.getCharWidth();
1327 lastMousePress = evt.getPoint();
1334 dragStretchGroup(evt);
1338 int res = pos.column;
1345 if ((editLastRes == -1) || (editLastRes == res))
1350 if ((res < av.getAlignment().getWidth()) && (res < editLastRes))
1352 // dragLeft, delete gap
1353 editSequence(false, false, res);
1357 editSequence(true, false, res);
1360 mouseDragging = true;
1361 if (scrollThread != null)
1363 scrollThread.setMousePosition(evt.getPoint());
1368 * Edits the sequence to insert or delete one or more gaps, in response to a
1369 * mouse drag or cursor mode command. The number of inserts/deletes may be
1370 * specified with the cursor command, or else depends on the mouse event
1371 * (normally one column, but potentially more for a fast mouse drag).
1373 * Delete gaps is limited to the number of gaps left of the cursor position
1374 * (mouse drag), or at or right of the cursor position (cursor mode).
1376 * In group editing mode (Ctrl or Cmd down), the edit acts on all sequences in
1377 * the current selection group.
1379 * In locked editing mode (with a selection group present), inserts/deletions
1380 * within the selection group are limited to its boundaries (and edits outside
1381 * the group stop at its border).
1384 * true to insert gaps, false to delete gaps
1386 * (unused parameter)
1388 * the column at which to perform the action; the number of columns
1389 * affected depends on <code>this.editLastRes</code> (cursor column
1392 synchronized void editSequence(boolean insertGap, boolean editSeq,
1396 int fixedRight = -1;
1397 boolean fixedColumns = false;
1398 SequenceGroup sg = av.getSelectionGroup();
1400 final SequenceI seq = av.getAlignment().getSequenceAt(editStartSeq);
1402 // No group, but the sequence may represent a group
1403 if (!groupEditing && av.hasHiddenRows())
1405 if (av.isHiddenRepSequence(seq))
1407 sg = av.getRepresentedSequences(seq);
1408 groupEditing = true;
1412 StringBuilder message = new StringBuilder(64); // for status bar
1415 * make a name for the edit action, for
1416 * status bar message and Undo/Redo menu
1418 String label = null;
1421 message.append("Edit group:");
1422 label = MessageManager.getString("action.edit_group");
1426 message.append("Edit sequence: " + seq.getName());
1427 label = seq.getName();
1428 if (label.length() > 10)
1430 label = label.substring(0, 10);
1432 label = MessageManager.formatMessage("label.edit_params",
1438 * initialise the edit command if there is not
1439 * already one being extended
1441 if (editCommand == null)
1443 editCommand = new EditCommand(label);
1448 message.append(" insert ");
1452 message.append(" delete ");
1455 message.append(Math.abs(startres - editLastRes) + " gaps.");
1456 ap.alignFrame.setStatus(message.toString());
1459 * is there a selection group containing the sequence being edited?
1460 * if so the boundary of the group is the limit of the edit
1461 * (but the edit may be inside or outside the selection group)
1463 boolean inSelectionGroup = sg != null
1464 && sg.getSequences(av.getHiddenRepSequences()).contains(seq);
1465 if (groupEditing || inSelectionGroup)
1467 fixedColumns = true;
1469 // sg might be null as the user may only see 1 sequence,
1470 // but the sequence represents a group
1473 if (!av.isHiddenRepSequence(seq))
1478 sg = av.getRepresentedSequences(seq);
1481 fixedLeft = sg.getStartRes();
1482 fixedRight = sg.getEndRes();
1484 if ((startres < fixedLeft && editLastRes >= fixedLeft)
1485 || (startres >= fixedLeft && editLastRes < fixedLeft)
1486 || (startres > fixedRight && editLastRes <= fixedRight)
1487 || (startres <= fixedRight && editLastRes > fixedRight))
1493 if (fixedLeft > startres)
1495 fixedRight = fixedLeft - 1;
1498 else if (fixedRight < startres)
1500 fixedLeft = fixedRight;
1505 if (av.hasHiddenColumns())
1507 fixedColumns = true;
1508 int y1 = av.getAlignment().getHiddenColumns()
1509 .getNextHiddenBoundary(true, startres);
1510 int y2 = av.getAlignment().getHiddenColumns()
1511 .getNextHiddenBoundary(false, startres);
1513 if ((insertGap && startres > y1 && editLastRes < y1)
1514 || (!insertGap && startres < y2 && editLastRes > y2))
1520 // System.out.print(y1+" "+y2+" "+fixedLeft+" "+fixedRight+"~~");
1521 // Selection spans a hidden region
1522 if (fixedLeft < y1 && (fixedRight > y2 || fixedRight == -1))
1530 fixedRight = y2 - 1;
1535 boolean success = doEditSequence(insertGap, editSeq, startres,
1536 fixedRight, fixedColumns, sg);
1539 * report what actually happened (might be less than
1540 * what was requested), by inspecting the edit commands added
1542 String msg = getEditStatusMessage(editCommand);
1543 ap.alignFrame.setStatus(msg == null ? " " : msg);
1549 editLastRes = startres;
1550 seqCanvas.repaint();
1554 * A helper method that performs the requested editing to insert or delete
1555 * gaps (if possible). Answers true if the edit was successful, false if could
1556 * only be performed in part or not at all. Failure may occur in 'locked edit'
1557 * mode, when an insertion requires a matching gapped position (or column) to
1558 * delete, and deletion requires an adjacent gapped position (or column) to
1562 * true if inserting gap(s), false if deleting
1564 * (unused parameter, currently always false)
1566 * the column at which to perform the edit
1568 * fixed right boundary column of a locked edit (within or to the
1569 * left of a selection group)
1570 * @param fixedColumns
1571 * true if this is a locked edit
1573 * the sequence group (if group edit is being performed)
1576 protected boolean doEditSequence(final boolean insertGap,
1577 final boolean editSeq, final int startres, int fixedRight,
1578 final boolean fixedColumns, final SequenceGroup sg)
1580 final SequenceI seq = av.getAlignment().getSequenceAt(editStartSeq);
1581 SequenceI[] seqs = new SequenceI[] { seq };
1585 List<SequenceI> vseqs = sg.getSequences(av.getHiddenRepSequences());
1586 int g, groupSize = vseqs.size();
1587 SequenceI[] groupSeqs = new SequenceI[groupSize];
1588 for (g = 0; g < groupSeqs.length; g++)
1590 groupSeqs[g] = vseqs.get(g);
1596 // If the user has selected the whole sequence, and is dragging to
1597 // the right, we can still extend the alignment and selectionGroup
1598 if (sg.getStartRes() == 0 && sg.getEndRes() == fixedRight
1599 && sg.getEndRes() == av.getAlignment().getWidth() - 1)
1602 av.getAlignment().getWidth() + startres - editLastRes);
1603 fixedRight = sg.getEndRes();
1606 // Is it valid with fixed columns??
1607 // Find the next gap before the end
1608 // of the visible region boundary
1609 boolean blank = false;
1610 for (; fixedRight > editLastRes; fixedRight--)
1614 for (g = 0; g < groupSize; g++)
1616 for (int j = 0; j < startres - editLastRes; j++)
1619 .isGap(groupSeqs[g].getCharAt(fixedRight - j)))
1634 if (sg.getSize() == av.getAlignment().getHeight())
1636 if ((av.hasHiddenColumns()
1637 && startres < av.getAlignment().getHiddenColumns()
1638 .getNextHiddenBoundary(false, startres)))
1643 int alWidth = av.getAlignment().getWidth();
1644 if (av.hasHiddenRows())
1646 int hwidth = av.getAlignment().getHiddenSequences()
1648 if (hwidth > alWidth)
1653 // We can still insert gaps if the selectionGroup
1654 // contains all the sequences
1655 sg.setEndRes(sg.getEndRes() + startres - editLastRes);
1656 fixedRight = alWidth + startres - editLastRes;
1666 else if (!insertGap)
1668 // / Are we able to delete?
1669 // ie are all columns blank?
1671 for (g = 0; g < groupSize; g++)
1673 for (int j = startres; j < editLastRes; j++)
1675 if (groupSeqs[g].getLength() <= j)
1680 if (!Comparison.isGap(groupSeqs[g].getCharAt(j)))
1682 // Not a gap, block edit not valid
1691 // dragging to the right
1692 if (fixedColumns && fixedRight != -1)
1694 for (int j = editLastRes; j < startres; j++)
1696 insertGap(j, groupSeqs, fixedRight);
1701 appendEdit(Action.INSERT_GAP, groupSeqs, startres,
1702 startres - editLastRes, false);
1707 // dragging to the left
1708 if (fixedColumns && fixedRight != -1)
1710 for (int j = editLastRes; j > startres; j--)
1712 deleteChar(startres, groupSeqs, fixedRight);
1717 appendEdit(Action.DELETE_GAP, groupSeqs, startres,
1718 editLastRes - startres, false);
1725 * editing a single sequence
1729 // dragging to the right
1730 if (fixedColumns && fixedRight != -1)
1732 for (int j = editLastRes; j < startres; j++)
1734 if (!insertGap(j, seqs, fixedRight))
1737 * e.g. cursor mode command specified
1738 * more inserts than are possible
1746 appendEdit(Action.INSERT_GAP, seqs, editLastRes,
1747 startres - editLastRes, false);
1754 // dragging to the left
1755 if (fixedColumns && fixedRight != -1)
1757 for (int j = editLastRes; j > startres; j--)
1759 if (!Comparison.isGap(seq.getCharAt(startres)))
1763 deleteChar(startres, seqs, fixedRight);
1768 // could be a keyboard edit trying to delete none gaps
1770 for (int m = startres; m < editLastRes; m++)
1772 if (!Comparison.isGap(seq.getCharAt(m)))
1780 appendEdit(Action.DELETE_GAP, seqs, startres, max, false);
1785 {// insertGap==false AND editSeq==TRUE;
1786 if (fixedColumns && fixedRight != -1)
1788 for (int j = editLastRes; j < startres; j++)
1790 insertGap(j, seqs, fixedRight);
1795 appendEdit(Action.INSERT_NUC, seqs, editLastRes,
1796 startres - editLastRes, false);
1806 * Constructs an informative status bar message while dragging to insert or
1807 * delete gaps. Answers null if inserts and deletes cancel out.
1809 * @param editCommand
1810 * a command containing the list of individual edits
1813 protected static String getEditStatusMessage(EditCommand editCommand)
1815 if (editCommand == null)
1821 * add any inserts, and subtract any deletes,
1822 * not counting those auto-inserted when doing a 'locked edit'
1823 * (so only counting edits 'under the cursor')
1826 for (Edit cmd : editCommand.getEdits())
1828 if (!cmd.isSystemGenerated())
1830 count += cmd.getAction() == Action.INSERT_GAP ? cmd.getNumber()
1838 * inserts and deletes cancel out
1843 String msgKey = count > 1 ? "label.insert_gaps"
1844 : (count == 1 ? "label.insert_gap"
1845 : (count == -1 ? "label.delete_gap"
1846 : "label.delete_gaps"));
1847 count = Math.abs(count);
1849 return MessageManager.formatMessage(msgKey, String.valueOf(count));
1853 * Inserts one gap at column j, deleting the right-most gapped column up to
1854 * (and including) fixedColumn. Returns true if the edit is successful, false
1855 * if no blank column is available to allow the insertion to be balanced by a
1860 * @param fixedColumn
1863 boolean insertGap(int j, SequenceI[] seq, int fixedColumn)
1865 int blankColumn = fixedColumn;
1866 for (int s = 0; s < seq.length; s++)
1868 // Find the next gap before the end of the visible region boundary
1869 // If lastCol > j, theres a boundary after the gap insertion
1871 for (blankColumn = fixedColumn; blankColumn > j; blankColumn--)
1873 if (Comparison.isGap(seq[s].getCharAt(blankColumn)))
1875 // Theres a space, so break and insert the gap
1880 if (blankColumn <= j)
1882 blankColumn = fixedColumn;
1888 appendEdit(Action.DELETE_GAP, seq, blankColumn, 1, true);
1890 appendEdit(Action.INSERT_GAP, seq, j, 1, false);
1896 * Helper method to add and perform one edit action
1902 * @param systemGenerated
1903 * true if the edit is a 'balancing' delete (or insert) to match a
1904 * user's insert (or delete) in a locked editing region
1906 protected void appendEdit(Action action, SequenceI[] seq, int pos,
1907 int count, boolean systemGenerated)
1910 final Edit edit = new EditCommand().new Edit(action, seq, pos, count,
1911 av.getAlignment().getGapCharacter());
1912 edit.setSystemGenerated(systemGenerated);
1914 editCommand.appendEdit(edit, av.getAlignment(), true, null);
1918 * Deletes the character at column j, and inserts a gap at fixedColumn, in
1919 * each of the given sequences. The caller should ensure that all sequences
1920 * are gapped in column j.
1924 * @param fixedColumn
1926 void deleteChar(int j, SequenceI[] seqs, int fixedColumn)
1928 appendEdit(Action.DELETE_GAP, seqs, j, 1, false);
1930 appendEdit(Action.INSERT_GAP, seqs, fixedColumn, 1, true);
1934 * On reentering the panel, stops any scrolling that was started on dragging
1940 public void mouseEntered(MouseEvent e)
1950 * On leaving the panel, if the mouse is being dragged, starts a thread to
1951 * scroll it until the mouse is released (in unwrapped mode only)
1956 public void mouseExited(MouseEvent e)
1958 lastMousePosition = null;
1959 ap.alignFrame.setStatus(" ");
1960 if (av.getWrapAlignment())
1965 if (mouseDragging && scrollThread == null)
1967 scrollThread = new ScrollThread();
1972 * Handler for double-click on a position with one or more sequence features.
1973 * Opens the Amend Features dialog to allow feature details to be amended, or
1974 * the feature deleted.
1977 public void mouseClicked(MouseEvent evt)
1979 SequenceGroup sg = null;
1980 MousePos pos = findMousePosition(evt);
1981 if (pos.isOverAnnotation() || pos.seqIndex == -1 || pos.column == -1)
1986 if (evt.getClickCount() > 1)
1988 sg = av.getSelectionGroup();
1989 if (sg != null && sg.getSize() == 1
1990 && sg.getEndRes() - sg.getStartRes() < 2)
1992 av.setSelectionGroup(null);
1995 int column = pos.column;
1998 * find features at the position (if not gapped), or straddling
1999 * the position (if at a gap)
2001 SequenceI sequence = av.getAlignment().getSequenceAt(pos.seqIndex);
2002 List<SequenceFeature> features = seqCanvas.getFeatureRenderer()
2003 .findFeaturesAtColumn(sequence, column + 1);
2005 if (!features.isEmpty())
2008 * highlight the first feature at the position on the alignment
2010 SearchResultsI highlight = new SearchResults();
2011 highlight.addResult(sequence, features.get(0).getBegin(), features
2013 seqCanvas.highlightSearchResults(highlight, false);
2016 * open the Amend Features dialog; clear highlighting afterwards,
2017 * whether changes were made or not
2019 List<SequenceI> seqs = Collections.singletonList(sequence);
2020 seqCanvas.getFeatureRenderer().amendFeatures(seqs, features, false,
2022 av.setSearchResults(null); // clear highlighting
2023 seqCanvas.repaint(); // draw new/amended features
2029 public void mouseWheelMoved(MouseWheelEvent e)
2032 double wheelRotation = e.getPreciseWheelRotation();
2033 if (wheelRotation > 0)
2035 if (e.isShiftDown())
2037 av.getRanges().scrollRight(true);
2042 av.getRanges().scrollUp(false);
2045 else if (wheelRotation < 0)
2047 if (e.isShiftDown())
2049 av.getRanges().scrollRight(false);
2053 av.getRanges().scrollUp(true);
2058 * update status bar and tooltip for new position
2059 * (need to synthesize a mouse movement to refresh tooltip)
2062 ToolTipManager.sharedInstance().mouseMoved(e);
2071 protected void doMousePressedDefineMode(MouseEvent evt, MousePos pos)
2073 if (pos.isOverAnnotation() || pos.seqIndex == -1 || pos.column == -1)
2078 final int res = pos.column;
2079 final int seq = pos.seqIndex;
2081 updateOverviewAndStructs = false;
2083 startWrapBlock = wrappedBlock;
2085 SequenceI sequence = av.getAlignment().getSequenceAt(seq);
2087 if ((sequence == null) || (res > sequence.getLength()))
2092 stretchGroup = av.getSelectionGroup();
2094 if (stretchGroup == null || !stretchGroup.contains(sequence, res))
2096 stretchGroup = av.getAlignment().findGroup(sequence, res);
2097 if (stretchGroup != null)
2099 // only update the current selection if the popup menu has a group to
2101 av.setSelectionGroup(stretchGroup);
2105 if (evt.isPopupTrigger()) // Mac: mousePressed
2107 showPopupMenu(evt, pos);
2112 * defer right-mouse click handling to mouseReleased on Windows
2113 * (where isPopupTrigger() will answer true)
2114 * NB isRightMouseButton is also true for Cmd-click on Mac
2116 if (SwingUtilities.isRightMouseButton(evt) && !Platform.isAMac())
2123 seqCanvas.cursorX = res;
2124 seqCanvas.cursorY = seq;
2125 seqCanvas.repaint();
2129 if (stretchGroup == null)
2131 createStretchGroup(res, sequence);
2134 if (stretchGroup != null)
2136 stretchGroup.addPropertyChangeListener(seqCanvas);
2139 seqCanvas.repaint();
2142 private void createStretchGroup(int res, SequenceI sequence)
2144 // Only if left mouse button do we want to change group sizes
2145 // define a new group here
2146 SequenceGroup sg = new SequenceGroup();
2147 sg.setStartRes(res);
2149 sg.addSequence(sequence, false);
2150 av.setSelectionGroup(sg);
2153 if (av.getConservationSelected())
2155 SliderPanel.setConservationSlider(ap, av.getResidueShading(),
2159 if (av.getAbovePIDThreshold())
2161 SliderPanel.setPIDSliderSource(ap, av.getResidueShading(),
2164 // TODO: stretchGroup will always be not null. Is this a merge error ?
2165 // or is there a threading issue here?
2166 if ((stretchGroup != null) && (stretchGroup.getEndRes() == res))
2168 // Edit end res position of selected group
2169 changeEndRes = true;
2171 else if ((stretchGroup != null) && (stretchGroup.getStartRes() == res))
2173 // Edit end res position of selected group
2174 changeStartRes = true;
2176 stretchGroup.getWidth();
2181 * Build and show a pop-up menu at the right-click mouse position
2186 void showPopupMenu(MouseEvent evt, MousePos pos)
2188 final int column = pos.column;
2189 final int seq = pos.seqIndex;
2190 SequenceI sequence = av.getAlignment().getSequenceAt(seq);
2191 List<SequenceFeature> features = ap.getFeatureRenderer()
2192 .findFeaturesAtColumn(sequence, column + 1);
2194 PopupMenu pop = new PopupMenu(ap, null, features);
2195 pop.show(this, evt.getX(), evt.getY());
2199 * Update the display after mouse up on a selection or group
2202 * mouse released event details
2204 * true if this event is happening after a mouse drag (rather than a
2207 protected void doMouseReleasedDefineMode(MouseEvent evt,
2210 if (stretchGroup == null)
2215 stretchGroup.removePropertyChangeListener(seqCanvas);
2217 // always do this - annotation has own state
2218 // but defer colourscheme update until hidden sequences are passed in
2219 boolean vischange = stretchGroup.recalcConservation(true);
2220 updateOverviewAndStructs |= vischange && av.isSelectionDefinedGroup()
2222 if (stretchGroup.cs != null)
2226 stretchGroup.cs.alignmentChanged(stretchGroup,
2227 av.getHiddenRepSequences());
2230 ResidueShaderI groupColourScheme = stretchGroup
2231 .getGroupColourScheme();
2232 String name = stretchGroup.getName();
2233 if (stretchGroup.cs.conservationApplied())
2235 SliderPanel.setConservationSlider(ap, groupColourScheme, name);
2237 if (stretchGroup.cs.getThreshold() > 0)
2239 SliderPanel.setPIDSliderSource(ap, groupColourScheme, name);
2242 PaintRefresher.Refresh(this, av.getSequenceSetId());
2243 // TODO: structure colours only need updating if stretchGroup used to or now
2244 // does contain sequences with structure views
2245 ap.paintAlignment(updateOverviewAndStructs, updateOverviewAndStructs);
2246 updateOverviewAndStructs = false;
2247 changeEndRes = false;
2248 changeStartRes = false;
2249 stretchGroup = null;
2254 * Resizes the borders of a selection group depending on the direction of
2259 protected void dragStretchGroup(MouseEvent evt)
2261 if (stretchGroup == null)
2266 MousePos pos = findMousePosition(evt);
2267 if (pos.isOverAnnotation() || pos.column == -1 || pos.seqIndex == -1)
2272 int res = pos.column;
2273 int y = pos.seqIndex;
2275 if (wrappedBlock != startWrapBlock)
2280 res = Math.min(res, av.getAlignment().getWidth()-1);
2282 if (stretchGroup.getEndRes() == res)
2284 // Edit end res position of selected group
2285 changeEndRes = true;
2287 else if (stretchGroup.getStartRes() == res)
2289 // Edit start res position of selected group
2290 changeStartRes = true;
2293 if (res < av.getRanges().getStartRes())
2295 res = av.getRanges().getStartRes();
2300 if (res > (stretchGroup.getStartRes() - 1))
2302 stretchGroup.setEndRes(res);
2303 updateOverviewAndStructs |= av.isSelectionDefinedGroup();
2306 else if (changeStartRes)
2308 if (res < (stretchGroup.getEndRes() + 1))
2310 stretchGroup.setStartRes(res);
2311 updateOverviewAndStructs |= av.isSelectionDefinedGroup();
2315 int dragDirection = 0;
2321 else if (y < oldSeq)
2326 while ((y != oldSeq) && (oldSeq > -1)
2327 && (y < av.getAlignment().getHeight()))
2329 // This routine ensures we don't skip any sequences, as the
2330 // selection is quite slow.
2331 Sequence seq = (Sequence) av.getAlignment().getSequenceAt(oldSeq);
2333 oldSeq += dragDirection;
2340 Sequence nextSeq = (Sequence) av.getAlignment().getSequenceAt(oldSeq);
2342 if (stretchGroup.getSequences(null).contains(nextSeq))
2344 stretchGroup.deleteSequence(seq, false);
2345 updateOverviewAndStructs |= av.isSelectionDefinedGroup();
2351 stretchGroup.addSequence(seq, false);
2354 stretchGroup.addSequence(nextSeq, false);
2355 updateOverviewAndStructs |= av.isSelectionDefinedGroup();
2364 mouseDragging = true;
2366 if (scrollThread != null)
2368 scrollThread.setMousePosition(evt.getPoint());
2372 * construct a status message showing the range of the selection
2374 StringBuilder status = new StringBuilder(64);
2375 List<SequenceI> seqs = stretchGroup.getSequences();
2376 String name = seqs.get(0).getName();
2377 if (name.length() > 20)
2379 name = name.substring(0, 20);
2381 status.append(name).append(" - ");
2382 name = seqs.get(seqs.size() - 1).getName();
2383 if (name.length() > 20)
2385 name = name.substring(0, 20);
2387 status.append(name).append(" ");
2388 int startRes = stretchGroup.getStartRes();
2389 status.append(" cols ").append(String.valueOf(startRes + 1))
2391 int endRes = stretchGroup.getEndRes();
2392 status.append(String.valueOf(endRes + 1));
2393 status.append(" (").append(String.valueOf(seqs.size())).append(" x ")
2394 .append(String.valueOf(endRes - startRes + 1)).append(")");
2395 ap.alignFrame.setStatus(status.toString());
2399 * Stops the scroll thread if it is running
2401 void stopScrolling()
2403 if (scrollThread != null)
2405 scrollThread.stopScrolling();
2406 scrollThread = null;
2408 mouseDragging = false;
2412 * Starts a thread to scroll the alignment, towards a given mouse position
2413 * outside the panel bounds
2417 void startScrolling(Point mousePos)
2419 if (scrollThread == null)
2421 scrollThread = new ScrollThread();
2424 mouseDragging = true;
2425 scrollThread.setMousePosition(mousePos);
2429 * Performs scrolling of the visible alignment left, right, up or down
2431 class ScrollThread extends Thread
2433 private Point mousePos;
2435 private volatile boolean threadRunning = true;
2440 public ScrollThread()
2442 setName("SeqPanel$ScrollThread");
2447 * Sets the position of the mouse that determines the direction of the
2452 public void setMousePosition(Point p)
2458 * Sets a flag that will cause the thread to exit
2460 public void stopScrolling()
2462 threadRunning = false;
2466 * Scrolls the alignment left or right, and/or up or down, depending on the
2467 * last notified mouse position, until the limit of the alignment is
2468 * reached, or a flag is set to stop the scroll
2473 while (threadRunning && mouseDragging)
2475 if (mousePos != null)
2477 boolean scrolled = false;
2478 ViewportRanges ranges = SeqPanel.this.av.getRanges();
2485 // mouse is above this panel - try scroll up
2486 scrolled = ranges.scrollUp(true);
2488 else if (mousePos.y >= getHeight())
2490 // mouse is below this panel - try scroll down
2491 scrolled = ranges.scrollUp(false);
2495 * scroll left or right
2499 scrolled |= ranges.scrollRight(false);
2501 else if (mousePos.x >= getWidth())
2503 scrolled |= ranges.scrollRight(true);
2508 * we have reached the limit of the visible alignment - quit
2510 threadRunning = false;
2511 SeqPanel.this.ap.repaint();
2518 } catch (Exception ex)
2526 * modify current selection according to a received message.
2529 public void selection(SequenceGroup seqsel, ColumnSelection colsel,
2530 HiddenColumns hidden, SelectionSource source)
2532 // TODO: fix this hack - source of messages is align viewport, but SeqPanel
2533 // handles selection messages...
2534 // TODO: extend config options to allow user to control if selections may be
2535 // shared between viewports.
2536 boolean iSentTheSelection = (av == source
2537 || (source instanceof AlignViewport
2538 && ((AlignmentViewport) source).getSequenceSetId()
2539 .equals(av.getSequenceSetId())));
2541 if (iSentTheSelection)
2543 // respond to our own event by updating dependent dialogs
2544 if (ap.getCalculationDialog() != null)
2546 ap.getCalculationDialog().validateCalcTypes();
2552 // process further ?
2553 if (!av.followSelection)
2559 * Ignore the selection if there is one of our own pending.
2561 if (av.isSelectionGroupChanged(false) || av.isColSelChanged(false))
2567 * Check for selection in a view of which this one is a dna/protein
2570 if (selectionFromTranslation(seqsel, colsel, hidden, source))
2575 // do we want to thread this ? (contention with seqsel and colsel locks, I
2578 * only copy colsel if there is a real intersection between
2579 * sequence selection and this panel's alignment
2581 boolean repaint = false;
2582 boolean copycolsel = false;
2584 SequenceGroup sgroup = null;
2585 if (seqsel != null && seqsel.getSize() > 0)
2587 if (av.getAlignment() == null)
2589 Cache.log.warn("alignviewport av SeqSetId=" + av.getSequenceSetId()
2590 + " ViewId=" + av.getViewId()
2591 + " 's alignment is NULL! returning immediately.");
2594 sgroup = seqsel.intersect(av.getAlignment(),
2595 (av.hasHiddenRows()) ? av.getHiddenRepSequences() : null);
2596 if ((sgroup != null && sgroup.getSize() > 0))
2601 if (sgroup != null && sgroup.getSize() > 0)
2603 av.setSelectionGroup(sgroup);
2607 av.setSelectionGroup(null);
2609 av.isSelectionGroupChanged(true);
2614 // the current selection is unset or from a previous message
2615 // so import the new colsel.
2616 if (colsel == null || colsel.isEmpty())
2618 if (av.getColumnSelection() != null)
2620 av.getColumnSelection().clear();
2626 // TODO: shift colSel according to the intersecting sequences
2627 if (av.getColumnSelection() == null)
2629 av.setColumnSelection(new ColumnSelection(colsel));
2633 av.getColumnSelection().setElementsFrom(colsel,
2634 av.getAlignment().getHiddenColumns());
2637 av.isColSelChanged(true);
2641 if (copycolsel && av.hasHiddenColumns()
2642 && (av.getAlignment().getHiddenColumns() == null))
2644 System.err.println("Bad things");
2646 if (repaint) // always true!
2648 // probably finessing with multiple redraws here
2649 PaintRefresher.Refresh(this, av.getSequenceSetId());
2650 // ap.paintAlignment(false);
2653 // lastly, update dependent dialogs
2654 if (ap.getCalculationDialog() != null)
2656 ap.getCalculationDialog().validateCalcTypes();
2662 * If this panel is a cdna/protein translation view of the selection source,
2663 * tries to map the source selection to a local one, and returns true. Else
2670 protected boolean selectionFromTranslation(SequenceGroup seqsel,
2671 ColumnSelection colsel, HiddenColumns hidden,
2672 SelectionSource source)
2674 if (!(source instanceof AlignViewportI))
2678 final AlignViewportI sourceAv = (AlignViewportI) source;
2679 if (sourceAv.getCodingComplement() != av
2680 && av.getCodingComplement() != sourceAv)
2686 * Map sequence selection
2688 SequenceGroup sg = MappingUtils.mapSequenceGroup(seqsel, sourceAv, av);
2689 av.setSelectionGroup(sg);
2690 av.isSelectionGroupChanged(true);
2693 * Map column selection
2695 // ColumnSelection cs = MappingUtils.mapColumnSelection(colsel, sourceAv,
2697 ColumnSelection cs = new ColumnSelection();
2698 HiddenColumns hs = new HiddenColumns();
2699 MappingUtils.mapColumnSelection(colsel, hidden, sourceAv, av, cs, hs);
2700 av.setColumnSelection(cs);
2701 boolean hiddenChanged = av.getAlignment().setHiddenColumns(hs);
2703 // lastly, update any dependent dialogs
2704 if (ap.getCalculationDialog() != null)
2706 ap.getCalculationDialog().validateCalcTypes();
2710 * repaint alignment, and also Overview or Structure
2711 * if hidden column selection has changed
2713 ap.paintAlignment(hiddenChanged, hiddenChanged);
2720 * @return null or last search results handled by this panel
2722 public SearchResultsI getLastSearchResults()
2724 return lastSearchResults;