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.AlignmentI;
29 import jalview.datamodel.ColumnSelection;
30 import jalview.datamodel.HiddenColumns;
31 import jalview.datamodel.SearchResultMatchI;
32 import jalview.datamodel.SearchResults;
33 import jalview.datamodel.SearchResultsI;
34 import jalview.datamodel.Sequence;
35 import jalview.datamodel.SequenceFeature;
36 import jalview.datamodel.SequenceGroup;
37 import jalview.datamodel.SequenceI;
38 import jalview.io.SequenceAnnotationReport;
39 import jalview.renderer.ResidueShaderI;
40 import jalview.schemes.ResidueProperties;
41 import jalview.structure.SelectionListener;
42 import jalview.structure.SelectionSource;
43 import jalview.structure.SequenceListener;
44 import jalview.structure.StructureSelectionManager;
45 import jalview.structure.VamsasSource;
46 import jalview.util.Comparison;
47 import jalview.util.MappingUtils;
48 import jalview.util.MessageManager;
49 import jalview.util.Platform;
50 import jalview.viewmodel.AlignmentViewport;
52 import java.awt.BorderLayout;
53 import java.awt.Color;
55 import java.awt.FontMetrics;
56 import java.awt.Point;
57 import java.awt.event.MouseEvent;
58 import java.awt.event.MouseListener;
59 import java.awt.event.MouseMotionListener;
60 import java.awt.event.MouseWheelEvent;
61 import java.awt.event.MouseWheelListener;
62 import java.util.Collections;
63 import java.util.List;
65 import javax.swing.JPanel;
66 import javax.swing.SwingUtilities;
67 import javax.swing.ToolTipManager;
73 * @version $Revision: 1.130 $
75 public class SeqPanel extends JPanel
76 implements MouseListener, MouseMotionListener, MouseWheelListener,
77 SequenceListener, SelectionListener
79 private static final int MAX_TOOLTIP_LENGTH = 300;
81 public SeqCanvas seqCanvas;
83 public AlignmentPanel ap;
86 * last column position for mouseMoved event
88 private int lastMouseColumn;
91 * last sequence offset for mouseMoved event
93 private int lastMouseSeq;
95 protected int editLastRes;
97 protected int editStartSeq;
99 protected AlignViewport av;
101 ScrollThread scrollThread = null;
103 boolean mouseDragging = false;
105 boolean editingSeqs = false;
107 boolean groupEditing = false;
109 // ////////////////////////////////////////
110 // ///Everything below this is for defining the boundary of the rubberband
111 // ////////////////////////////////////////
114 boolean changeEndSeq = false;
116 boolean changeStartSeq = false;
118 boolean changeEndRes = false;
120 boolean changeStartRes = false;
122 SequenceGroup stretchGroup = null;
124 boolean remove = false;
126 Point lastMousePress;
128 boolean mouseWheelPressed = false;
130 StringBuffer keyboardNo1;
132 StringBuffer keyboardNo2;
134 java.net.URL linkImageURL;
136 private final SequenceAnnotationReport seqARep;
138 StringBuilder tooltipText = new StringBuilder();
142 EditCommand editCommand;
144 StructureSelectionManager ssm;
146 SearchResultsI lastSearchResults;
149 * Creates a new SeqPanel object
154 public SeqPanel(AlignViewport viewport, AlignmentPanel alignPanel)
156 linkImageURL = getClass().getResource("/images/link.gif");
157 seqARep = new SequenceAnnotationReport(linkImageURL.toString());
158 ToolTipManager.sharedInstance().registerComponent(this);
159 ToolTipManager.sharedInstance().setInitialDelay(0);
160 ToolTipManager.sharedInstance().setDismissDelay(10000);
162 setBackground(Color.white);
164 seqCanvas = new SeqCanvas(alignPanel);
165 setLayout(new BorderLayout());
166 add(seqCanvas, BorderLayout.CENTER);
168 this.ap = alignPanel;
170 if (!viewport.isDataset())
172 addMouseMotionListener(this);
173 addMouseListener(this);
174 addMouseWheelListener(this);
175 ssm = viewport.getStructureSelectionManager();
176 ssm.addStructureViewerListener(this);
177 ssm.addSelectionListener(this);
180 lastMouseColumn = -1;
184 int startWrapBlock = -1;
186 int wrappedBlock = -1;
189 * Returns the aligned sequence position (base 0) at the mouse position, or
190 * the closest visible one
195 int findColumn(MouseEvent evt)
200 int startRes = av.getRanges().getStartRes();
201 if (av.getWrapAlignment())
204 int hgap = av.getCharHeight();
205 if (av.getScaleAboveWrapped())
207 hgap += av.getCharHeight();
210 int cHeight = av.getAlignment().getHeight() * av.getCharHeight()
211 + hgap + seqCanvas.getAnnotationHeight();
214 y = Math.max(0, y - hgap);
215 x = Math.max(0, x - seqCanvas.getLabelWidthWest());
217 int cwidth = seqCanvas.getWrappedCanvasWidth(this.getWidth());
223 wrappedBlock = y / cHeight;
224 wrappedBlock += startRes / cwidth;
225 // allow for wrapped view scrolled right (possible from Overview)
226 int startOffset = startRes % cwidth;
227 res = wrappedBlock * cwidth + startOffset
228 + +Math.min(cwidth - 1, x / av.getCharWidth());
232 if (x > seqCanvas.getX() + seqCanvas.getWidth())
234 // make sure we calculate relative to visible alignment, rather than
236 x = seqCanvas.getX() + seqCanvas.getWidth();
238 res = (x / av.getCharWidth()) + startRes;
239 if (res > av.getRanges().getEndRes())
242 res = av.getRanges().getEndRes();
246 if (av.hasHiddenColumns())
248 res = av.getAlignment().getHiddenColumns()
249 .visibleToAbsoluteColumn(res);
256 int findSeq(MouseEvent evt)
261 if (av.getWrapAlignment())
263 int hgap = av.getCharHeight();
264 if (av.getScaleAboveWrapped())
266 hgap += av.getCharHeight();
269 int cHeight = av.getAlignment().getHeight() * av.getCharHeight()
270 + hgap + seqCanvas.getAnnotationHeight();
274 seq = Math.min((y % cHeight) / av.getCharHeight(),
275 av.getAlignment().getHeight() - 1);
280 (y / av.getCharHeight()) + av.getRanges().getStartSeq(),
281 av.getAlignment().getHeight() - 1);
288 * When all of a sequence of edits are complete, put the resulting edit list
289 * on the history stack (undo list), and reset flags for editing in progress.
295 if (editCommand != null && editCommand.getSize() > 0)
297 ap.alignFrame.addHistoryItem(editCommand);
298 av.firePropertyChange("alignment", null,
299 av.getAlignment().getSequences());
304 * Tidy up come what may...
309 groupEditing = false;
318 seqCanvas.cursorY = getKeyboardNo1() - 1;
319 scrollToVisible(true);
322 void setCursorColumn()
324 seqCanvas.cursorX = getKeyboardNo1() - 1;
325 scrollToVisible(true);
328 void setCursorRowAndColumn()
330 if (keyboardNo2 == null)
332 keyboardNo2 = new StringBuffer();
336 seqCanvas.cursorX = getKeyboardNo1() - 1;
337 seqCanvas.cursorY = getKeyboardNo2() - 1;
338 scrollToVisible(true);
342 void setCursorPosition()
344 SequenceI sequence = av.getAlignment().getSequenceAt(seqCanvas.cursorY);
346 seqCanvas.cursorX = sequence.findIndex(getKeyboardNo1()) - 1;
347 scrollToVisible(true);
350 void moveCursor(int dx, int dy)
352 seqCanvas.cursorX += dx;
353 seqCanvas.cursorY += dy;
355 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
357 if (av.hasHiddenColumns() && !hidden.isVisible(seqCanvas.cursorX))
359 int original = seqCanvas.cursorX - dx;
360 int maxWidth = av.getAlignment().getWidth();
362 if (!hidden.isVisible(seqCanvas.cursorX))
364 int visx = hidden.absoluteToVisibleColumn(seqCanvas.cursorX - dx);
365 int[] region = hidden.getRegionWithEdgeAtRes(visx);
367 if (region != null) // just in case
372 seqCanvas.cursorX = region[1] + 1;
377 seqCanvas.cursorX = region[0] - 1;
380 seqCanvas.cursorX = (seqCanvas.cursorX < 0) ? 0 : seqCanvas.cursorX;
383 if (seqCanvas.cursorX >= maxWidth
384 || !hidden.isVisible(seqCanvas.cursorX))
386 seqCanvas.cursorX = original;
390 scrollToVisible(false);
394 * Scroll to make the cursor visible in the viewport.
397 * just jump to the location rather than scrolling
399 void scrollToVisible(boolean jump)
401 if (seqCanvas.cursorX < 0)
403 seqCanvas.cursorX = 0;
405 else if (seqCanvas.cursorX > av.getAlignment().getWidth() - 1)
407 seqCanvas.cursorX = av.getAlignment().getWidth() - 1;
410 if (seqCanvas.cursorY < 0)
412 seqCanvas.cursorY = 0;
414 else if (seqCanvas.cursorY > av.getAlignment().getHeight() - 1)
416 seqCanvas.cursorY = av.getAlignment().getHeight() - 1;
421 boolean repaintNeeded = true;
424 // only need to repaint if the viewport did not move, as otherwise it will
426 repaintNeeded = !av.getRanges().setViewportLocation(seqCanvas.cursorX,
431 if (av.getWrapAlignment())
433 // scrollToWrappedVisible expects x-value to have hidden cols subtracted
434 int x = av.getAlignment().getHiddenColumns()
435 .absoluteToVisibleColumn(seqCanvas.cursorX);
436 av.getRanges().scrollToWrappedVisible(x);
440 av.getRanges().scrollToVisible(seqCanvas.cursorX,
445 if (av.getAlignment().getHiddenColumns().isVisible(seqCanvas.cursorX))
447 setStatusMessage(av.getAlignment().getSequenceAt(seqCanvas.cursorY),
448 seqCanvas.cursorX, seqCanvas.cursorY);
458 void setSelectionAreaAtCursor(boolean topLeft)
460 SequenceI sequence = av.getAlignment().getSequenceAt(seqCanvas.cursorY);
462 if (av.getSelectionGroup() != null)
464 SequenceGroup sg = av.getSelectionGroup();
465 // Find the top and bottom of this group
466 int min = av.getAlignment().getHeight(), max = 0;
467 for (int i = 0; i < sg.getSize(); i++)
469 int index = av.getAlignment().findIndex(sg.getSequenceAt(i));
484 sg.setStartRes(seqCanvas.cursorX);
485 if (sg.getEndRes() < seqCanvas.cursorX)
487 sg.setEndRes(seqCanvas.cursorX);
490 min = seqCanvas.cursorY;
494 sg.setEndRes(seqCanvas.cursorX);
495 if (sg.getStartRes() > seqCanvas.cursorX)
497 sg.setStartRes(seqCanvas.cursorX);
500 max = seqCanvas.cursorY + 1;
505 // Only the user can do this
506 av.setSelectionGroup(null);
510 // Now add any sequences between min and max
511 sg.getSequences(null).clear();
512 for (int i = min; i < max; i++)
514 sg.addSequence(av.getAlignment().getSequenceAt(i), false);
519 if (av.getSelectionGroup() == null)
521 SequenceGroup sg = new SequenceGroup();
522 sg.setStartRes(seqCanvas.cursorX);
523 sg.setEndRes(seqCanvas.cursorX);
524 sg.addSequence(sequence, false);
525 av.setSelectionGroup(sg);
528 ap.paintAlignment(false, false);
532 void insertGapAtCursor(boolean group)
534 groupEditing = group;
535 editStartSeq = seqCanvas.cursorY;
536 editLastRes = seqCanvas.cursorX;
537 editSequence(true, false, seqCanvas.cursorX + getKeyboardNo1());
541 void deleteGapAtCursor(boolean group)
543 groupEditing = group;
544 editStartSeq = seqCanvas.cursorY;
545 editLastRes = seqCanvas.cursorX + getKeyboardNo1();
546 editSequence(false, false, seqCanvas.cursorX);
550 void insertNucAtCursor(boolean group, String nuc)
552 // TODO not called - delete?
553 groupEditing = group;
554 editStartSeq = seqCanvas.cursorY;
555 editLastRes = seqCanvas.cursorX;
556 editSequence(false, true, seqCanvas.cursorX + getKeyboardNo1());
560 void numberPressed(char value)
562 if (keyboardNo1 == null)
564 keyboardNo1 = new StringBuffer();
567 if (keyboardNo2 != null)
569 keyboardNo2.append(value);
573 keyboardNo1.append(value);
581 if (keyboardNo1 != null)
583 int value = Integer.parseInt(keyboardNo1.toString());
587 } catch (Exception x)
598 if (keyboardNo2 != null)
600 int value = Integer.parseInt(keyboardNo2.toString());
604 } catch (Exception x)
618 public void mouseReleased(MouseEvent evt)
620 boolean didDrag = mouseDragging; // did we come here after a drag
621 mouseDragging = false;
622 mouseWheelPressed = false;
624 if (evt.isPopupTrigger()) // Windows: mouseReleased
633 doMouseReleasedDefineMode(evt, didDrag);
647 public void mousePressed(MouseEvent evt)
649 lastMousePress = evt.getPoint();
651 if (SwingUtilities.isMiddleMouseButton(evt))
653 mouseWheelPressed = true;
657 boolean isControlDown = Platform.isControlDown(evt);
658 if (evt.isShiftDown() || isControlDown)
668 doMousePressedDefineMode(evt);
672 int seq = findSeq(evt);
673 int res = findColumn(evt);
675 if (seq < 0 || res < 0)
680 if ((seq < av.getAlignment().getHeight())
681 && (res < av.getAlignment().getSequenceAt(seq).getLength()))
698 public void mouseOverSequence(SequenceI sequence, int index, int pos)
700 String tmp = sequence.hashCode() + " " + index + " " + pos;
702 if (lastMessage == null || !lastMessage.equals(tmp))
704 // System.err.println("mouseOver Sequence: "+tmp);
705 ssm.mouseOverSequence(sequence, index, pos, av);
711 * Highlight the mapped region described by the search results object (unless
712 * unchanged). This supports highlight of protein while mousing over linked
713 * cDNA and vice versa. The status bar is also updated to show the location of
714 * the start of the highlighted region.
717 public void highlightSequence(SearchResultsI results)
719 if (results == null || results.equals(lastSearchResults))
723 lastSearchResults = results;
725 boolean wasScrolled = false;
727 if (av.isFollowHighlight())
729 // don't allow highlight of protein/cDNA to also scroll a complementary
730 // panel,as this sets up a feedback loop (scrolling panel 1 causes moused
731 // over residue to change abruptly, causing highlighted residue in panel 2
732 // to change, causing a scroll in panel 1 etc)
733 ap.setToScrollComplementPanel(false);
734 wasScrolled = ap.scrollToPosition(results, false);
737 seqCanvas.revalidate();
739 ap.setToScrollComplementPanel(true);
742 boolean noFastPaint = wasScrolled && av.getWrapAlignment();
743 if (seqCanvas.highlightSearchResults(results, noFastPaint))
745 setStatusMessage(results);
750 public VamsasSource getVamsasSource()
752 return this.ap == null ? null : this.ap.av;
756 public void updateColours(SequenceI seq, int index)
758 System.out.println("update the seqPanel colours");
763 * Action on mouse movement is to update the status bar to show the current
764 * sequence position, and (if features are shown) to show any features at the
765 * position in a tooltip. Does nothing if the mouse move does not change
771 public void mouseMoved(MouseEvent evt)
775 // This is because MacOSX creates a mouseMoved
776 // If control is down, other platforms will not.
780 final int column = findColumn(evt);
781 final int seq = findSeq(evt);
783 if (column < 0 || seq < 0 || seq >= av.getAlignment().getHeight())
788 if (column == lastMouseColumn && seq == lastMouseSeq)
791 * just a pixel move without change of residue
795 lastMouseColumn = column;
798 SequenceI sequence = av.getAlignment().getSequenceAt(seq);
800 if (column >= sequence.getLength())
806 * set status bar message, returning residue position in sequence
808 boolean isGapped = Comparison.isGap(sequence.getCharAt(column));
809 final int pos = setStatusMessage(sequence, column, seq);
810 if (ssm != null && !isGapped)
812 mouseOverSequence(sequence, column, pos);
815 tooltipText.setLength(6); // Cuts the buffer back to <html>
817 SequenceGroup[] groups = av.getAlignment().findAllGroups(sequence);
820 for (int g = 0; g < groups.length; g++)
822 if (groups[g].getStartRes() <= column
823 && groups[g].getEndRes() >= column)
825 if (!groups[g].getName().startsWith("JTreeGroup")
826 && !groups[g].getName().startsWith("JGroup"))
828 tooltipText.append(groups[g].getName());
831 if (groups[g].getDescription() != null)
833 tooltipText.append(": " + groups[g].getDescription());
840 * add any features at the position to the tooltip; if over a gap, only
841 * add features that straddle the gap (pos may be the residue before or
844 if (av.isShowSequenceFeatures())
846 List<SequenceFeature> features = ap.getFeatureRenderer()
847 .findFeaturesAtColumn(sequence, column + 1);
848 seqARep.appendFeatures(tooltipText, pos, features,
849 this.ap.getSeqPanel().seqCanvas.fr);
851 if (tooltipText.length() == 6) // <html>
853 setToolTipText(null);
858 if (tooltipText.length() > MAX_TOOLTIP_LENGTH) // constant
860 tooltipText.setLength(MAX_TOOLTIP_LENGTH);
861 tooltipText.append("...");
863 String textString = tooltipText.toString();
864 if (lastTooltip == null || !lastTooltip.equals(textString))
866 String formattedTooltipText = JvSwingUtils.wrapTooltip(true,
868 setToolTipText(formattedTooltipText);
869 lastTooltip = textString;
874 private Point lastp = null;
879 * @see javax.swing.JComponent#getToolTipLocation(java.awt.event.MouseEvent)
882 public Point getToolTipLocation(MouseEvent event)
884 int x = event.getX(), w = getWidth();
885 int wdth = (w - x < 200) ? -(w / 2) : 5; // switch sides when tooltip is too
888 if (!event.isShiftDown() || p == null)
890 p = (tooltipText != null && tooltipText.length() > 6)
891 ? new Point(event.getX() + wdth, event.getY() - 20)
895 * TODO: try to modify position region is not obcured by tooltip
903 * set when the current UI interaction has resulted in a change that requires
904 * shading in overviews and structures to be recalculated. this could be
905 * changed to a something more expressive that indicates what actually has
906 * changed, so selective redraws can be applied (ie. only structures, only
909 private boolean updateOverviewAndStructs = false; // TODO: refactor to avcontroller
912 * set if av.getSelectionGroup() refers to a group that is defined on the
913 * alignment view, rather than a transient selection
915 // private boolean editingDefinedGroup = false; // TODO: refactor to
916 // avcontroller or viewModel
919 * Sets the status message in alignment panel, showing the sequence number
920 * (index) and id, and residue and residue position if not at a gap, for the
921 * given sequence and column position. Returns the residue position returned
922 * by Sequence.findPosition. Note this may be for the nearest adjacent residue
923 * if at a gapped position.
926 * aligned sequence object
930 * index of sequence in alignment
931 * @return sequence position of residue at column, or adjacent residue if at a
934 int setStatusMessage(SequenceI sequence, final int column, int seqIndex)
936 char sequenceChar = sequence.getCharAt(column);
937 int pos = sequence.findPosition(column);
938 setStatusMessage(sequence, seqIndex, sequenceChar, pos);
944 * Builds the status message for the current cursor location and writes it to
945 * the status bar, for example
948 * Sequence 3 ID: FER1_SOLLC
949 * Sequence 5 ID: FER1_PEA Residue: THR (4)
950 * Sequence 5 ID: FER1_PEA Residue: B (3)
951 * Sequence 6 ID: O.niloticus.3 Nucleotide: Uracil (2)
956 * sequence position in the alignment (1..)
957 * @param sequenceChar
958 * the character under the cursor
960 * the sequence residue position (if not over a gap)
962 protected void setStatusMessage(SequenceI sequence, int seqIndex,
963 char sequenceChar, int residuePos)
965 StringBuilder text = new StringBuilder(32);
968 * Sequence number (if known), and sequence name.
970 String seqno = seqIndex == -1 ? "" : " " + (seqIndex + 1);
971 text.append("Sequence").append(seqno).append(" ID: ")
972 .append(sequence.getName());
974 String residue = null;
977 * Try to translate the display character to residue name (null for gap).
979 boolean isGapped = Comparison.isGap(sequenceChar);
983 boolean nucleotide = av.getAlignment().isNucleotide();
984 String displayChar = String.valueOf(sequenceChar);
987 residue = ResidueProperties.nucleotideName.get(displayChar);
991 residue = "X".equalsIgnoreCase(displayChar) ? "X"
992 : ("*".equals(displayChar) ? "STOP"
993 : ResidueProperties.aa2Triplet.get(displayChar));
995 text.append(" ").append(nucleotide ? "Nucleotide" : "Residue")
996 .append(": ").append(residue == null ? displayChar : residue);
998 text.append(" (").append(Integer.toString(residuePos)).append(")");
1000 ap.alignFrame.statusBar.setText(text.toString());
1004 * Set the status bar message to highlight the first matched position in
1009 private void setStatusMessage(SearchResultsI results)
1011 AlignmentI al = this.av.getAlignment();
1012 int sequenceIndex = al.findIndex(results);
1013 if (sequenceIndex == -1)
1017 SequenceI ds = al.getSequenceAt(sequenceIndex).getDatasetSequence();
1018 for (SearchResultMatchI m : results.getResults())
1020 SequenceI seq = m.getSequence();
1021 if (seq.getDatasetSequence() != null)
1023 seq = seq.getDatasetSequence();
1028 int start = m.getStart();
1029 setStatusMessage(seq, sequenceIndex, seq.getCharAt(start - 1),
1040 public void mouseDragged(MouseEvent evt)
1042 if (mouseWheelPressed)
1044 boolean inSplitFrame = ap.av.getCodingComplement() != null;
1045 boolean copyChanges = inSplitFrame && av.isProteinFontAsCdna();
1047 int oldWidth = av.getCharWidth();
1049 // Which is bigger, left-right or up-down?
1050 if (Math.abs(evt.getY() - lastMousePress.getY()) > Math
1051 .abs(evt.getX() - lastMousePress.getX()))
1054 * on drag up or down, decrement or increment font size
1056 int fontSize = av.font.getSize();
1057 boolean fontChanged = false;
1059 if (evt.getY() < lastMousePress.getY())
1064 else if (evt.getY() > lastMousePress.getY())
1077 Font newFont = new Font(av.font.getName(), av.font.getStyle(),
1079 av.setFont(newFont, true);
1080 av.setCharWidth(oldWidth);
1084 ap.av.getCodingComplement().setFont(newFont, true);
1085 SplitFrame splitFrame = (SplitFrame) ap.alignFrame
1086 .getSplitViewContainer();
1087 splitFrame.adjustLayout();
1088 splitFrame.repaint();
1095 * on drag left or right, decrement or increment character width
1098 if (evt.getX() < lastMousePress.getX() && av.getCharWidth() > 1)
1100 newWidth = av.getCharWidth() - 1;
1101 av.setCharWidth(newWidth);
1103 else if (evt.getX() > lastMousePress.getX())
1105 newWidth = av.getCharWidth() + 1;
1106 av.setCharWidth(newWidth);
1110 ap.paintAlignment(false, false);
1114 * need to ensure newWidth is set on cdna, regardless of which
1115 * panel the mouse drag happened in; protein will compute its
1116 * character width as 1:1 or 3:1
1118 av.getCodingComplement().setCharWidth(newWidth);
1119 SplitFrame splitFrame = (SplitFrame) ap.alignFrame
1120 .getSplitViewContainer();
1121 splitFrame.adjustLayout();
1122 splitFrame.repaint();
1127 FontMetrics fm = getFontMetrics(av.getFont());
1128 av.validCharWidth = fm.charWidth('M') <= av.getCharWidth();
1130 lastMousePress = evt.getPoint();
1137 doMouseDraggedDefineMode(evt);
1141 int res = findColumn(evt);
1148 if ((editLastRes == -1) || (editLastRes == res))
1153 if ((res < av.getAlignment().getWidth()) && (res < editLastRes))
1155 // dragLeft, delete gap
1156 editSequence(false, false, res);
1160 editSequence(true, false, res);
1163 mouseDragging = true;
1164 if ((scrollThread != null) && (scrollThread.isRunning()))
1166 scrollThread.setEvent(evt);
1171 * Edits the sequence to insert or delete one or more gaps, in response to a
1172 * mouse drag or cursor mode command. The number of inserts/deletes may be
1173 * specified with the cursor command, or else depends on the mouse event
1174 * (normally one column, but potentially more for a fast mouse drag).
1176 * Delete gaps is limited to the number of gaps left of the cursor position
1177 * (mouse drag), or at or right of the cursor position (cursor mode).
1179 * In group editing mode (Ctrl or Cmd down), the edit acts on all sequences in
1180 * the current selection group.
1182 * In locked editing mode (with a selection group present), inserts/deletions
1183 * within the selection group are limited to its boundaries (and edits outside
1184 * the group stop at its border).
1187 * true to insert gaps, false to delete gaps
1189 * (unused parameter)
1191 * the column at which to perform the action; the number of columns
1192 * affected depends on <code>this.editLastRes</code> (cursor column
1195 synchronized void editSequence(boolean insertGap, boolean editSeq,
1199 int fixedRight = -1;
1200 boolean fixedColumns = false;
1201 SequenceGroup sg = av.getSelectionGroup();
1203 final SequenceI seq = av.getAlignment().getSequenceAt(editStartSeq);
1205 // No group, but the sequence may represent a group
1206 if (!groupEditing && av.hasHiddenRows())
1208 if (av.isHiddenRepSequence(seq))
1210 sg = av.getRepresentedSequences(seq);
1211 groupEditing = true;
1216 * make a name for the edit action, for
1217 * status bar message and Undo/Redo menu
1219 String label = null;
1222 label = MessageManager.getString("action.edit_group");
1226 label = seq.getName();
1227 if (label.length() > 10)
1229 label = label.substring(0, 10);
1231 label = MessageManager.formatMessage("label.edit_params",
1237 * initialise the edit command if there is not
1238 * already one being extended
1240 if (editCommand == null)
1242 editCommand = new EditCommand(label);
1246 * is there a selection group containing the sequence being edited?
1247 * if so the boundary of the group is the limit of the edit
1248 * (but the edit may be inside or outside the selection group)
1250 boolean inSelectionGroup = sg != null
1251 && sg.getSequences(av.getHiddenRepSequences()).contains(seq);
1252 if (groupEditing || inSelectionGroup)
1254 fixedColumns = true;
1256 // sg might be null as the user may only see 1 sequence,
1257 // but the sequence represents a group
1260 if (!av.isHiddenRepSequence(seq))
1265 sg = av.getRepresentedSequences(seq);
1268 fixedLeft = sg.getStartRes();
1269 fixedRight = sg.getEndRes();
1271 if ((startres < fixedLeft && editLastRes >= fixedLeft)
1272 || (startres >= fixedLeft && editLastRes < fixedLeft)
1273 || (startres > fixedRight && editLastRes <= fixedRight)
1274 || (startres <= fixedRight && editLastRes > fixedRight))
1280 if (fixedLeft > startres)
1282 fixedRight = fixedLeft - 1;
1285 else if (fixedRight < startres)
1287 fixedLeft = fixedRight;
1292 if (av.hasHiddenColumns())
1294 fixedColumns = true;
1295 int y1 = av.getAlignment().getHiddenColumns()
1296 .getNextHiddenBoundary(true, startres);
1297 int y2 = av.getAlignment().getHiddenColumns()
1298 .getNextHiddenBoundary(false, startres);
1300 if ((insertGap && startres > y1 && editLastRes < y1)
1301 || (!insertGap && startres < y2 && editLastRes > y2))
1307 // System.out.print(y1+" "+y2+" "+fixedLeft+" "+fixedRight+"~~");
1308 // Selection spans a hidden region
1309 if (fixedLeft < y1 && (fixedRight > y2 || fixedRight == -1))
1317 fixedRight = y2 - 1;
1322 boolean success = doEditSequence(insertGap, editSeq, startres,
1323 fixedRight, fixedColumns, sg);
1326 * report what actually happened (might be less than
1327 * what was requested), by inspecting the edit commands added
1329 String msg = getEditStatusMessage(editCommand);
1330 ap.alignFrame.statusBar.setText(msg == null ? " " : msg);
1336 editLastRes = startres;
1337 seqCanvas.repaint();
1341 * A helper method that performs the requested editing to insert or delete
1342 * gaps (if possible). Answers true if the edit was successful, false if could
1343 * only be performed in part or not at all. Failure may occur in 'locked edit'
1344 * mode, when an insertion requires a matching gapped position (or column) to
1345 * delete, and deletion requires an adjacent gapped position (or column) to
1349 * true if inserting gap(s), false if deleting
1351 * (unused parameter, currently always false)
1353 * the column at which to perform the edit
1355 * fixed right boundary column of a locked edit (within or to the
1356 * left of a selection group)
1357 * @param fixedColumns
1358 * true if this is a locked edit
1360 * the sequence group (if group edit is being performed)
1363 protected boolean doEditSequence(final boolean insertGap,
1364 final boolean editSeq, final int startres, int fixedRight,
1365 final boolean fixedColumns, final SequenceGroup sg)
1367 final SequenceI seq = av.getAlignment().getSequenceAt(editStartSeq);
1368 SequenceI[] seqs = new SequenceI[] { seq };
1372 List<SequenceI> vseqs = sg.getSequences(av.getHiddenRepSequences());
1373 int g, groupSize = vseqs.size();
1374 SequenceI[] groupSeqs = new SequenceI[groupSize];
1375 for (g = 0; g < groupSeqs.length; g++)
1377 groupSeqs[g] = vseqs.get(g);
1383 // If the user has selected the whole sequence, and is dragging to
1384 // the right, we can still extend the alignment and selectionGroup
1385 if (sg.getStartRes() == 0 && sg.getEndRes() == fixedRight
1386 && sg.getEndRes() == av.getAlignment().getWidth() - 1)
1389 av.getAlignment().getWidth() + startres - editLastRes);
1390 fixedRight = sg.getEndRes();
1393 // Is it valid with fixed columns??
1394 // Find the next gap before the end
1395 // of the visible region boundary
1396 boolean blank = false;
1397 for (; fixedRight > editLastRes; fixedRight--)
1401 for (g = 0; g < groupSize; g++)
1403 for (int j = 0; j < startres - editLastRes; j++)
1406 .isGap(groupSeqs[g].getCharAt(fixedRight - j)))
1421 if (sg.getSize() == av.getAlignment().getHeight())
1423 if ((av.hasHiddenColumns()
1424 && startres < av.getAlignment().getHiddenColumns()
1425 .getNextHiddenBoundary(false, startres)))
1430 int alWidth = av.getAlignment().getWidth();
1431 if (av.hasHiddenRows())
1433 int hwidth = av.getAlignment().getHiddenSequences()
1435 if (hwidth > alWidth)
1440 // We can still insert gaps if the selectionGroup
1441 // contains all the sequences
1442 sg.setEndRes(sg.getEndRes() + startres - editLastRes);
1443 fixedRight = alWidth + startres - editLastRes;
1453 else if (!insertGap)
1455 // / Are we able to delete?
1456 // ie are all columns blank?
1458 for (g = 0; g < groupSize; g++)
1460 for (int j = startres; j < editLastRes; j++)
1462 if (groupSeqs[g].getLength() <= j)
1467 if (!Comparison.isGap(groupSeqs[g].getCharAt(j)))
1469 // Not a gap, block edit not valid
1478 // dragging to the right
1479 if (fixedColumns && fixedRight != -1)
1481 for (int j = editLastRes; j < startres; j++)
1483 insertGap(j, groupSeqs, fixedRight);
1488 appendEdit(Action.INSERT_GAP, groupSeqs, startres,
1489 startres - editLastRes, false);
1494 // dragging to the left
1495 if (fixedColumns && fixedRight != -1)
1497 for (int j = editLastRes; j > startres; j--)
1499 deleteChar(startres, groupSeqs, fixedRight);
1504 appendEdit(Action.DELETE_GAP, groupSeqs, startres,
1505 editLastRes - startres, false);
1512 * editing a single sequence
1516 // dragging to the right
1517 if (fixedColumns && fixedRight != -1)
1519 for (int j = editLastRes; j < startres; j++)
1521 if (!insertGap(j, seqs, fixedRight))
1524 * e.g. cursor mode command specified
1525 * more inserts than are possible
1533 appendEdit(Action.INSERT_GAP, seqs, editLastRes,
1534 startres - editLastRes, false);
1541 // dragging to the left
1542 if (fixedColumns && fixedRight != -1)
1544 for (int j = editLastRes; j > startres; j--)
1546 if (!Comparison.isGap(seq.getCharAt(startres)))
1550 deleteChar(startres, seqs, fixedRight);
1555 // could be a keyboard edit trying to delete none gaps
1557 for (int m = startres; m < editLastRes; m++)
1559 if (!Comparison.isGap(seq.getCharAt(m)))
1567 appendEdit(Action.DELETE_GAP, seqs, startres, max, false);
1572 {// insertGap==false AND editSeq==TRUE;
1573 if (fixedColumns && fixedRight != -1)
1575 for (int j = editLastRes; j < startres; j++)
1577 insertGap(j, seqs, fixedRight);
1582 appendEdit(Action.INSERT_NUC, seqs, editLastRes,
1583 startres - editLastRes, false);
1593 * Constructs an informative status bar message while dragging to insert or
1594 * delete gaps. Answers null if inserts and deletes cancel out.
1596 * @param editCommand
1597 * a command containing the list of individual edits
1600 protected static String getEditStatusMessage(EditCommand editCommand)
1602 if (editCommand == null)
1608 * add any inserts, and subtract any deletes,
1609 * not counting those auto-inserted when doing a 'locked edit'
1610 * (so only counting edits 'under the cursor')
1613 for (Edit cmd : editCommand.getEdits())
1615 if (!cmd.isSystemGenerated())
1617 count += cmd.getAction() == Action.INSERT_GAP ? cmd.getNumber()
1625 * inserts and deletes cancel out
1630 String msgKey = count > 1 ? "label.insert_gaps"
1631 : (count == 1 ? "label.insert_gap"
1632 : (count == -1 ? "label.delete_gap"
1633 : "label.delete_gaps"));
1634 count = Math.abs(count);
1636 return MessageManager.formatMessage(msgKey, String.valueOf(count));
1640 * Inserts one gap at column j, deleting the right-most gapped column up to
1641 * (and including) fixedColumn. Returns true if the edit is successful, false
1642 * if no blank column is available to allow the insertion to be balanced by a
1647 * @param fixedColumn
1650 boolean insertGap(int j, SequenceI[] seq, int fixedColumn)
1652 int blankColumn = fixedColumn;
1653 for (int s = 0; s < seq.length; s++)
1655 // Find the next gap before the end of the visible region boundary
1656 // If lastCol > j, theres a boundary after the gap insertion
1658 for (blankColumn = fixedColumn; blankColumn > j; blankColumn--)
1660 if (Comparison.isGap(seq[s].getCharAt(blankColumn)))
1662 // Theres a space, so break and insert the gap
1667 if (blankColumn <= j)
1669 blankColumn = fixedColumn;
1675 appendEdit(Action.DELETE_GAP, seq, blankColumn, 1, true);
1677 appendEdit(Action.INSERT_GAP, seq, j, 1, false);
1683 * Helper method to add and perform one edit action
1689 * @param systemGenerated
1690 * true if the edit is a 'balancing' delete (or insert) to match a
1691 * user's insert (or delete) in a locked editing region
1693 protected void appendEdit(Action action, SequenceI[] seq, int pos,
1694 int count, boolean systemGenerated)
1697 final Edit edit = new EditCommand().new Edit(action, seq, pos, count,
1698 av.getAlignment().getGapCharacter());
1699 edit.setSystemGenerated(systemGenerated);
1701 editCommand.appendEdit(edit, av.getAlignment(), true, null);
1705 * Deletes the character at column j, and inserts a gap at fixedColumn, in
1706 * each of the given sequences. The caller should ensure that all sequences
1707 * are gapped in column j.
1711 * @param fixedColumn
1713 void deleteChar(int j, SequenceI[] seqs, int fixedColumn)
1715 appendEdit(Action.DELETE_GAP, seqs, j, 1, false);
1717 appendEdit(Action.INSERT_GAP, seqs, fixedColumn, 1, true);
1727 public void mouseEntered(MouseEvent e)
1734 if ((scrollThread != null) && (scrollThread.isRunning()))
1736 scrollThread.stopScrolling();
1737 scrollThread = null;
1748 public void mouseExited(MouseEvent e)
1750 if (av.getWrapAlignment())
1755 if (mouseDragging && scrollThread == null)
1757 scrollThread = new ScrollThread();
1762 * Handler for double-click on a position with one or more sequence features.
1763 * Opens the Amend Features dialog to allow feature details to be amended, or
1764 * the feature deleted.
1767 public void mouseClicked(MouseEvent evt)
1769 SequenceGroup sg = null;
1770 SequenceI sequence = av.getAlignment().getSequenceAt(findSeq(evt));
1771 if (evt.getClickCount() > 1)
1773 sg = av.getSelectionGroup();
1774 if (sg != null && sg.getSize() == 1
1775 && sg.getEndRes() - sg.getStartRes() < 2)
1777 av.setSelectionGroup(null);
1780 int column = findColumn(evt);
1783 * find features at the position (if not gapped), or straddling
1784 * the position (if at a gap)
1786 List<SequenceFeature> features = seqCanvas.getFeatureRenderer()
1787 .findFeaturesAtColumn(sequence, column + 1);
1789 if (!features.isEmpty())
1792 * highlight the first feature at the position on the alignment
1794 SearchResultsI highlight = new SearchResults();
1795 highlight.addResult(sequence, features.get(0).getBegin(), features
1797 seqCanvas.highlightSearchResults(highlight, false);
1800 * open the Amend Features dialog; clear highlighting afterwards,
1801 * whether changes were made or not
1803 List<SequenceI> seqs = Collections.singletonList(sequence);
1804 seqCanvas.getFeatureRenderer().amendFeatures(seqs, features, false,
1806 av.setSearchResults(null); // clear highlighting
1807 seqCanvas.repaint(); // draw new/amended features
1813 public void mouseWheelMoved(MouseWheelEvent e)
1816 double wheelRotation = e.getPreciseWheelRotation();
1817 if (wheelRotation > 0)
1819 if (e.isShiftDown())
1821 av.getRanges().scrollRight(true);
1826 av.getRanges().scrollUp(false);
1829 else if (wheelRotation < 0)
1831 if (e.isShiftDown())
1833 av.getRanges().scrollRight(false);
1837 av.getRanges().scrollUp(true);
1842 * update status bar and tooltip for new position
1843 * (need to synthesize a mouse movement to refresh tooltip)
1846 ToolTipManager.sharedInstance().mouseMoved(e);
1855 public void doMousePressedDefineMode(MouseEvent evt)
1857 final int res = findColumn(evt);
1858 final int seq = findSeq(evt);
1860 updateOverviewAndStructs = false;
1862 startWrapBlock = wrappedBlock;
1864 if (av.getWrapAlignment() && seq > av.getAlignment().getHeight())
1866 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
1867 MessageManager.getString(
1868 "label.cannot_edit_annotations_in_wrapped_view"),
1869 MessageManager.getString("label.wrapped_view_no_edit"),
1870 JvOptionPane.WARNING_MESSAGE);
1874 if (seq < 0 || res < 0)
1879 SequenceI sequence = av.getAlignment().getSequenceAt(seq);
1881 if ((sequence == null) || (res > sequence.getLength()))
1886 stretchGroup = av.getSelectionGroup();
1888 if (stretchGroup == null || !stretchGroup.contains(sequence, res))
1890 stretchGroup = av.getAlignment().findGroup(sequence, res);
1891 if (stretchGroup != null)
1893 // only update the current selection if the popup menu has a group to
1895 av.setSelectionGroup(stretchGroup);
1899 if (evt.isPopupTrigger()) // Mac: mousePressed
1906 * defer right-mouse click handling to mouseReleased on Windows
1907 * (where isPopupTrigger() will answer true)
1908 * NB isRightMouseButton is also true for Cmd-click on Mac
1910 if (SwingUtilities.isRightMouseButton(evt) && !Platform.isAMac())
1917 seqCanvas.cursorX = findColumn(evt);
1918 seqCanvas.cursorY = findSeq(evt);
1919 seqCanvas.repaint();
1923 if (stretchGroup == null)
1925 createStretchGroup(res, sequence);
1928 if (stretchGroup != null)
1930 stretchGroup.addPropertyChangeListener(seqCanvas);
1933 seqCanvas.repaint();
1936 private void createStretchGroup(int res, SequenceI sequence)
1938 // Only if left mouse button do we want to change group sizes
1939 // define a new group here
1940 SequenceGroup sg = new SequenceGroup();
1941 sg.setStartRes(res);
1943 sg.addSequence(sequence, false);
1944 av.setSelectionGroup(sg);
1947 if (av.getConservationSelected())
1949 SliderPanel.setConservationSlider(ap, av.getResidueShading(),
1953 if (av.getAbovePIDThreshold())
1955 SliderPanel.setPIDSliderSource(ap, av.getResidueShading(),
1958 // TODO: stretchGroup will always be not null. Is this a merge error ?
1959 // or is there a threading issue here?
1960 if ((stretchGroup != null) && (stretchGroup.getEndRes() == res))
1962 // Edit end res position of selected group
1963 changeEndRes = true;
1965 else if ((stretchGroup != null) && (stretchGroup.getStartRes() == res))
1967 // Edit end res position of selected group
1968 changeStartRes = true;
1970 stretchGroup.getWidth();
1975 * Build and show a pop-up menu at the right-click mouse position
1981 void showPopupMenu(MouseEvent evt)
1983 final int column = findColumn(evt);
1984 final int seq = findSeq(evt);
1985 SequenceI sequence = av.getAlignment().getSequenceAt(seq);
1986 List<SequenceFeature> features = ap.getFeatureRenderer()
1987 .findFeaturesAtColumn(sequence, column + 1);
1989 PopupMenu pop = new PopupMenu(ap, null, features);
1990 pop.show(this, evt.getX(), evt.getY());
1994 * Update the display after mouse up on a selection or group
1997 * mouse released event details
1999 * true if this event is happening after a mouse drag (rather than a
2002 public void doMouseReleasedDefineMode(MouseEvent evt, boolean afterDrag)
2004 if (stretchGroup == null)
2009 stretchGroup.removePropertyChangeListener(seqCanvas);
2011 // always do this - annotation has own state
2012 // but defer colourscheme update until hidden sequences are passed in
2013 boolean vischange = stretchGroup.recalcConservation(true);
2014 updateOverviewAndStructs |= vischange && av.isSelectionDefinedGroup()
2016 if (stretchGroup.cs != null)
2018 stretchGroup.cs.alignmentChanged(stretchGroup,
2019 av.getHiddenRepSequences());
2021 ResidueShaderI groupColourScheme = stretchGroup
2022 .getGroupColourScheme();
2023 String name = stretchGroup.getName();
2024 if (stretchGroup.cs.conservationApplied())
2026 SliderPanel.setConservationSlider(ap, groupColourScheme, name);
2028 if (stretchGroup.cs.getThreshold() > 0)
2030 SliderPanel.setPIDSliderSource(ap, groupColourScheme, name);
2033 PaintRefresher.Refresh(this, av.getSequenceSetId());
2034 // TODO: structure colours only need updating if stretchGroup used to or now
2035 // does contain sequences with structure views
2036 ap.paintAlignment(updateOverviewAndStructs, updateOverviewAndStructs);
2037 updateOverviewAndStructs = false;
2038 changeEndRes = false;
2039 changeStartRes = false;
2040 stretchGroup = null;
2050 public void doMouseDraggedDefineMode(MouseEvent evt)
2052 int res = findColumn(evt);
2053 int y = findSeq(evt);
2055 if (wrappedBlock != startWrapBlock)
2060 if (stretchGroup == null)
2065 if (res >= av.getAlignment().getWidth())
2067 res = av.getAlignment().getWidth() - 1;
2070 if (stretchGroup.getEndRes() == res)
2072 // Edit end res position of selected group
2073 changeEndRes = true;
2075 else if (stretchGroup.getStartRes() == res)
2077 // Edit start res position of selected group
2078 changeStartRes = true;
2081 if (res < av.getRanges().getStartRes())
2083 res = av.getRanges().getStartRes();
2088 if (res > (stretchGroup.getStartRes() - 1))
2090 stretchGroup.setEndRes(res);
2091 updateOverviewAndStructs |= av.isSelectionDefinedGroup();
2094 else if (changeStartRes)
2096 if (res < (stretchGroup.getEndRes() + 1))
2098 stretchGroup.setStartRes(res);
2099 updateOverviewAndStructs |= av.isSelectionDefinedGroup();
2103 int dragDirection = 0;
2109 else if (y < oldSeq)
2114 while ((y != oldSeq) && (oldSeq > -1)
2115 && (y < av.getAlignment().getHeight()))
2117 // This routine ensures we don't skip any sequences, as the
2118 // selection is quite slow.
2119 Sequence seq = (Sequence) av.getAlignment().getSequenceAt(oldSeq);
2121 oldSeq += dragDirection;
2128 Sequence nextSeq = (Sequence) av.getAlignment().getSequenceAt(oldSeq);
2130 if (stretchGroup.getSequences(null).contains(nextSeq))
2132 stretchGroup.deleteSequence(seq, false);
2133 updateOverviewAndStructs |= av.isSelectionDefinedGroup();
2139 stretchGroup.addSequence(seq, false);
2142 stretchGroup.addSequence(nextSeq, false);
2143 updateOverviewAndStructs |= av.isSelectionDefinedGroup();
2152 mouseDragging = true;
2154 if ((scrollThread != null) && (scrollThread.isRunning()))
2156 scrollThread.setEvent(evt);
2160 * construct a status message showing the range of the selection
2162 StringBuilder status = new StringBuilder(64);
2163 List<SequenceI> seqs = stretchGroup.getSequences();
2164 String name = seqs.get(0).getName();
2165 if (name.length() > 20)
2167 name = name.substring(0, 20);
2169 status.append(name).append(" - ");
2170 name = seqs.get(seqs.size() - 1).getName();
2171 if (name.length() > 20)
2173 name = name.substring(0, 20);
2175 status.append(name).append(" ");
2176 int startRes = stretchGroup.getStartRes();
2177 status.append(" cols ").append(String.valueOf(startRes + 1))
2179 int endRes = stretchGroup.getEndRes();
2180 status.append(String.valueOf(endRes + 1));
2181 status.append(" (").append(String.valueOf(seqs.size())).append(" x ")
2182 .append(String.valueOf(endRes - startRes + 1)).append(")");
2183 ap.alignFrame.setStatus(status.toString());
2186 void scrollCanvas(MouseEvent evt)
2190 if ((scrollThread != null) && (scrollThread.isRunning()))
2192 scrollThread.stopScrolling();
2193 scrollThread = null;
2195 mouseDragging = false;
2199 if (scrollThread == null)
2201 scrollThread = new ScrollThread();
2204 mouseDragging = true;
2205 scrollThread.setEvent(evt);
2210 // this class allows scrolling off the bottom of the visible alignment
2211 class ScrollThread extends Thread
2215 private volatile boolean threadRunning = true;
2217 public ScrollThread()
2222 public void setEvent(MouseEvent e)
2227 public void stopScrolling()
2229 threadRunning = false;
2232 public boolean isRunning()
2234 return threadRunning;
2240 while (threadRunning)
2244 if (mouseDragging && (evt.getY() < 0)
2245 && (av.getRanges().getStartSeq() > 0))
2247 av.getRanges().scrollUp(true);
2250 if (mouseDragging && (evt.getY() >= getHeight()) && (av
2251 .getAlignment().getHeight() > av.getRanges().getEndSeq()))
2253 av.getRanges().scrollUp(false);
2256 if (mouseDragging && (evt.getX() < 0))
2258 av.getRanges().scrollRight(false);
2260 else if (mouseDragging && (evt.getX() >= getWidth()))
2262 av.getRanges().scrollRight(true);
2269 } catch (Exception ex)
2277 * modify current selection according to a received message.
2280 public void selection(SequenceGroup seqsel, ColumnSelection colsel,
2281 HiddenColumns hidden, SelectionSource source)
2283 // TODO: fix this hack - source of messages is align viewport, but SeqPanel
2284 // handles selection messages...
2285 // TODO: extend config options to allow user to control if selections may be
2286 // shared between viewports.
2287 boolean iSentTheSelection = (av == source
2288 || (source instanceof AlignViewport
2289 && ((AlignmentViewport) source).getSequenceSetId()
2290 .equals(av.getSequenceSetId())));
2292 if (iSentTheSelection)
2294 // respond to our own event by updating dependent dialogs
2295 if (ap.getCalculationDialog() != null)
2297 ap.getCalculationDialog().validateCalcTypes();
2303 // process further ?
2304 if (!av.followSelection)
2310 * Ignore the selection if there is one of our own pending.
2312 if (av.isSelectionGroupChanged(false) || av.isColSelChanged(false))
2318 * Check for selection in a view of which this one is a dna/protein
2321 if (selectionFromTranslation(seqsel, colsel, hidden, source))
2326 // do we want to thread this ? (contention with seqsel and colsel locks, I
2329 * only copy colsel if there is a real intersection between
2330 * sequence selection and this panel's alignment
2332 boolean repaint = false;
2333 boolean copycolsel = false;
2335 SequenceGroup sgroup = null;
2336 if (seqsel != null && seqsel.getSize() > 0)
2338 if (av.getAlignment() == null)
2340 Cache.log.warn("alignviewport av SeqSetId=" + av.getSequenceSetId()
2341 + " ViewId=" + av.getViewId()
2342 + " 's alignment is NULL! returning immediately.");
2345 sgroup = seqsel.intersect(av.getAlignment(),
2346 (av.hasHiddenRows()) ? av.getHiddenRepSequences() : null);
2347 if ((sgroup != null && sgroup.getSize() > 0))
2352 if (sgroup != null && sgroup.getSize() > 0)
2354 av.setSelectionGroup(sgroup);
2358 av.setSelectionGroup(null);
2360 av.isSelectionGroupChanged(true);
2365 // the current selection is unset or from a previous message
2366 // so import the new colsel.
2367 if (colsel == null || colsel.isEmpty())
2369 if (av.getColumnSelection() != null)
2371 av.getColumnSelection().clear();
2377 // TODO: shift colSel according to the intersecting sequences
2378 if (av.getColumnSelection() == null)
2380 av.setColumnSelection(new ColumnSelection(colsel));
2384 av.getColumnSelection().setElementsFrom(colsel,
2385 av.getAlignment().getHiddenColumns());
2388 av.isColSelChanged(true);
2392 if (copycolsel && av.hasHiddenColumns()
2393 && (av.getAlignment().getHiddenColumns() == null))
2395 System.err.println("Bad things");
2397 if (repaint) // always true!
2399 // probably finessing with multiple redraws here
2400 PaintRefresher.Refresh(this, av.getSequenceSetId());
2401 // ap.paintAlignment(false);
2404 // lastly, update dependent dialogs
2405 if (ap.getCalculationDialog() != null)
2407 ap.getCalculationDialog().validateCalcTypes();
2413 * If this panel is a cdna/protein translation view of the selection source,
2414 * tries to map the source selection to a local one, and returns true. Else
2421 protected boolean selectionFromTranslation(SequenceGroup seqsel,
2422 ColumnSelection colsel, HiddenColumns hidden,
2423 SelectionSource source)
2425 if (!(source instanceof AlignViewportI))
2429 final AlignViewportI sourceAv = (AlignViewportI) source;
2430 if (sourceAv.getCodingComplement() != av
2431 && av.getCodingComplement() != sourceAv)
2437 * Map sequence selection
2439 SequenceGroup sg = MappingUtils.mapSequenceGroup(seqsel, sourceAv, av);
2440 av.setSelectionGroup(sg);
2441 av.isSelectionGroupChanged(true);
2444 * Map column selection
2446 // ColumnSelection cs = MappingUtils.mapColumnSelection(colsel, sourceAv,
2448 ColumnSelection cs = new ColumnSelection();
2449 HiddenColumns hs = new HiddenColumns();
2450 MappingUtils.mapColumnSelection(colsel, hidden, sourceAv, av, cs, hs);
2451 av.setColumnSelection(cs);
2452 av.getAlignment().setHiddenColumns(hs);
2454 // lastly, update any dependent dialogs
2455 if (ap.getCalculationDialog() != null)
2457 ap.getCalculationDialog().validateCalcTypes();
2460 PaintRefresher.Refresh(this, av.getSequenceSetId());
2467 * @return null or last search results handled by this panel
2469 public SearchResultsI getLastSearchResults()
2471 return lastSearchResults;