X-Git-Url: http://source.jalview.org/gitweb/?a=blobdiff_plain;f=src%2Fjalview%2Fgui%2FSeqPanel.java;h=2df55ad53080f9a8c9eeb8a9c4f2a57636d9e3c1;hb=02cd62bf5796571c15a9d42774291bfa74b6b31c;hp=729249973e8dc8a0f533a3115947ece565f32609;hpb=c793942b6430bba9d9b6795e4afd8a1907c022d7;p=jalview.git diff --git a/src/jalview/gui/SeqPanel.java b/src/jalview/gui/SeqPanel.java index 7292499..2df55ad 100644 --- a/src/jalview/gui/SeqPanel.java +++ b/src/jalview/gui/SeqPanel.java @@ -25,6 +25,7 @@ import jalview.bin.Cache; import jalview.commands.EditCommand; import jalview.commands.EditCommand.Action; import jalview.commands.EditCommand.Edit; +import jalview.datamodel.AlignmentAnnotation; import jalview.datamodel.AlignmentI; import jalview.datamodel.ColumnSelection; import jalview.datamodel.HiddenColumns; @@ -48,12 +49,15 @@ import jalview.util.MappingUtils; import jalview.util.MessageManager; import jalview.util.Platform; import jalview.viewmodel.AlignmentViewport; +import jalview.viewmodel.ViewportRanges; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Point; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; @@ -62,8 +66,11 @@ import java.awt.event.MouseWheelListener; import java.util.Collections; import java.util.List; +import javax.swing.JLabel; import javax.swing.JPanel; +import javax.swing.JToolTip; import javax.swing.SwingUtilities; +import javax.swing.Timer; import javax.swing.ToolTipManager; /** @@ -76,6 +83,82 @@ public class SeqPanel extends JPanel implements MouseListener, MouseMotionListener, MouseWheelListener, SequenceListener, SelectionListener { + /* + * a class that holds computed mouse position + * - column of the alignment (0...) + * - sequence offset (0...) + * - annotation row offset (0...) + * where annotation offset is -1 unless the alignment is shown + * in wrapped mode, annotations are shown, and the mouse is + * over an annnotation row + */ + static class MousePos + { + /* + * alignment column position of cursor (0...) + */ + final int column; + + /* + * index in alignment of sequence under cursor, + * or nearest above if cursor is not over a sequence + */ + final int seqIndex; + + /* + * index in annotations array of annotation under the cursor + * (only possible in wrapped mode with annotations shown), + * or -1 if cursor is not over an annotation row + */ + final int annotationIndex; + + MousePos(int col, int seq, int ann) + { + column = col; + seqIndex = seq; + annotationIndex = ann; + } + + boolean isOverAnnotation() + { + return annotationIndex != -1; + } + + @Override + public boolean equals(Object obj) + { + if (obj == null || !(obj instanceof MousePos)) + { + return false; + } + MousePos o = (MousePos) obj; + boolean b = (column == o.column && seqIndex == o.seqIndex + && annotationIndex == o.annotationIndex); + // System.out.println(obj + (b ? "= " : "!= ") + this); + return b; + } + + /** + * A simple hashCode that ensures that instances that satisfy equals() have + * the same hashCode + */ + @Override + public int hashCode() + { + return column + seqIndex + annotationIndex; + } + + /** + * toString method for debug output purposes only + */ + @Override + public String toString() + { + return String.format("c%d:s%d:a%d", column, seqIndex, + annotationIndex); + } + } + private static final int MAX_TOOLTIP_LENGTH = 300; public SeqCanvas seqCanvas; @@ -83,14 +166,9 @@ public class SeqPanel extends JPanel public AlignmentPanel ap; /* - * last column position for mouseMoved event - */ - private int lastMouseColumn; - - /* - * last sequence offset for mouseMoved event + * last position for mouseMoved event */ - private int lastMouseSeq; + private MousePos lastMousePosition; protected int editLastRes; @@ -158,6 +236,8 @@ public class SeqPanel extends JPanel ToolTipManager.sharedInstance().registerComponent(this); ToolTipManager.sharedInstance().setInitialDelay(0); ToolTipManager.sharedInstance().setDismissDelay(10000); + + this.av = viewport; setBackground(Color.white); @@ -176,9 +256,6 @@ public class SeqPanel extends JPanel ssm.addStructureViewerListener(this); ssm.addSelectionListener(this); } - - lastMouseColumn = -1; - lastMouseSeq = -1; } int startWrapBlock = -1; @@ -186,6 +263,73 @@ public class SeqPanel extends JPanel int wrappedBlock = -1; /** + * Computes the column and sequence row (and possibly annotation row when in + * wrapped mode) for the given mouse position + * + * @param evt + * @return + */ + MousePos findMousePosition(MouseEvent evt) + { + int col = findColumn(evt); + int seqIndex = -1; + int annIndex = -1; + int y = evt.getY(); + + int charHeight = av.getCharHeight(); + int alignmentHeight = av.getAlignment().getHeight(); + if (av.getWrapAlignment()) + { + seqCanvas.calculateWrappedGeometry(seqCanvas.getWidth(), + seqCanvas.getHeight()); + + /* + * yPos modulo height of repeating width + */ + int yOffsetPx = y % seqCanvas.wrappedRepeatHeightPx; + + /* + * height of sequences plus space / scale above, + * plus gap between sequences and annotations + */ + int alignmentHeightPixels = seqCanvas.wrappedSpaceAboveAlignment + + alignmentHeight * charHeight + + SeqCanvas.SEQS_ANNOTATION_GAP; + if (yOffsetPx >= alignmentHeightPixels) + { + /* + * mouse is over annotations; find annotation index, also set + * last sequence above (for backwards compatible behaviour) + */ + AlignmentAnnotation[] anns = av.getAlignment() + .getAlignmentAnnotation(); + int rowOffsetPx = yOffsetPx - alignmentHeightPixels; + annIndex = AnnotationPanel.getRowIndex(rowOffsetPx, anns); + seqIndex = alignmentHeight - 1; + } + else + { + /* + * mouse is over sequence (or the space above sequences) + */ + yOffsetPx -= seqCanvas.wrappedSpaceAboveAlignment; + if (yOffsetPx >= 0) + { + seqIndex = Math.min(yOffsetPx / charHeight, alignmentHeight - 1); + } + } + } + else + { + ViewportRanges ranges = av.getRanges(); + seqIndex = Math.min((y / charHeight) + ranges.getStartSeq(), + alignmentHeight - 1); + seqIndex = Math.min(seqIndex, ranges.getEndSeq()); + } + + return new MousePos(col, seqIndex, annIndex); + } + /** * Returns the aligned sequence position (base 0) at the mouse position, or * the closest visible one * @@ -197,10 +341,11 @@ public class SeqPanel extends JPanel int res = 0; int x = evt.getX(); - int startRes = av.getRanges().getStartRes(); + final int startRes = av.getRanges().getStartRes(); + final int charWidth = av.getCharWidth(); + if (av.getWrapAlignment()) { - int hgap = av.getCharHeight(); if (av.getScaleAboveWrapped()) { @@ -212,35 +357,40 @@ public class SeqPanel extends JPanel int y = evt.getY(); y = Math.max(0, y - hgap); - x = Math.max(0, x - seqCanvas.getLabelWidthWest()); + x -= seqCanvas.getLabelWidthWest(); + if (x < 0) + { + // mouse is over left scale + return -1; + } int cwidth = seqCanvas.getWrappedCanvasWidth(this.getWidth()); if (cwidth < 1) { return 0; } + if (x >= cwidth * charWidth) + { + // mouse is over right scale + return -1; + } wrappedBlock = y / cHeight; wrappedBlock += startRes / cwidth; // allow for wrapped view scrolled right (possible from Overview) int startOffset = startRes % cwidth; res = wrappedBlock * cwidth + startOffset - + +Math.min(cwidth - 1, x / av.getCharWidth()); + + Math.min(cwidth - 1, x / charWidth); } else { - if (x > seqCanvas.getX() + seqCanvas.getWidth()) - { - // make sure we calculate relative to visible alignment, rather than - // right-hand gutter - x = seqCanvas.getX() + seqCanvas.getWidth(); - } - res = (x / av.getCharWidth()) + startRes; - if (res > av.getRanges().getEndRes()) - { - // moused off right - res = av.getRanges().getEndRes(); - } + /* + * make sure we calculate relative to visible alignment, + * rather than right-hand gutter + */ + x = Math.min(x, seqCanvas.getX() + seqCanvas.getWidth()); + res = (x / charWidth) + startRes; + res = Math.min(res, av.getRanges().getEndRes()); } if (av.hasHiddenColumns()) @@ -250,38 +400,6 @@ public class SeqPanel extends JPanel } return res; - - } - - int findSeq(MouseEvent evt) - { - int seq = 0; - int y = evt.getY(); - - if (av.getWrapAlignment()) - { - int hgap = av.getCharHeight(); - if (av.getScaleAboveWrapped()) - { - hgap += av.getCharHeight(); - } - - int cHeight = av.getAlignment().getHeight() * av.getCharHeight() - + hgap + seqCanvas.getAnnotationHeight(); - - y -= hgap; - - seq = Math.min((y % cHeight) / av.getCharHeight(), - av.getAlignment().getHeight() - 1); - } - else - { - seq = Math.min( - (y / av.getCharHeight()) + av.getRanges().getStartSeq(), - av.getAlignment().getHeight() - 1); - } - - return seq; } /** @@ -617,24 +735,31 @@ public class SeqPanel extends JPanel @Override public void mouseReleased(MouseEvent evt) { + MousePos pos = findMousePosition(evt); + if (pos.isOverAnnotation() || pos.seqIndex == -1 || pos.column == -1) + { + return; + } + boolean didDrag = mouseDragging; // did we come here after a drag mouseDragging = false; mouseWheelPressed = false; if (evt.isPopupTrigger()) // Windows: mouseReleased { - showPopupMenu(evt); + showPopupMenu(evt, pos); evt.consume(); return; } - if (!editingSeqs) + if (editingSeqs) + { + endEditing(); + } + else { doMouseReleasedDefineMode(evt, didDrag); - return; } - - endEditing(); } /** @@ -647,6 +772,11 @@ public class SeqPanel extends JPanel public void mousePressed(MouseEvent evt) { lastMousePress = evt.getPoint(); + MousePos pos = findMousePosition(evt); + if (pos.isOverAnnotation() || pos.seqIndex == -1 || pos.column == -1) + { + return; + } if (SwingUtilities.isMiddleMouseButton(evt)) { @@ -665,17 +795,12 @@ public class SeqPanel extends JPanel } else { - doMousePressedDefineMode(evt); + doMousePressedDefineMode(evt, pos); return; } - int seq = findSeq(evt); - int res = findColumn(evt); - - if (seq < 0 || res < 0) - { - return; - } + int seq = pos.seqIndex; + int res = pos.column; if ((seq < av.getAlignment().getHeight()) && (res < av.getAlignment().getSequenceAt(seq).getLength())) @@ -694,6 +819,8 @@ public class SeqPanel extends JPanel String lastMessage; + private String formattedTooltipText; + @Override public void mouseOverSequence(SequenceI sequence, int index, int pos) { @@ -739,8 +866,8 @@ public class SeqPanel extends JPanel ap.setToScrollComplementPanel(true); } - boolean noFastPaint = wasScrolled && av.getWrapAlignment(); - if (seqCanvas.highlightSearchResults(results, noFastPaint)) + boolean fastPaint = !(wasScrolled && av.getWrapAlignment()); + if (seqCanvas.highlightSearchResults(results, fastPaint)) { setStatusMessage(results); } @@ -777,23 +904,32 @@ public class SeqPanel extends JPanel mouseDragged(evt); } - final int column = findColumn(evt); - final int seq = findSeq(evt); + final MousePos mousePos = findMousePosition(evt); + if (mousePos.equals(lastMousePosition)) + { + /* + * just a pixel move without change of 'cell' + */ + return; + } + lastMousePosition = mousePos; - if (column < 0 || seq < 0 || seq >= av.getAlignment().getHeight()) + if (mousePos.isOverAnnotation()) { - lastMouseSeq = -1; + mouseMovedOverAnnotation(mousePos); return; } - if (column == lastMouseColumn && seq == lastMouseSeq) + final int seq = mousePos.seqIndex; + + final int column = mousePos.column; + if (column < 0 || seq < 0 || seq >= av.getAlignment().getHeight()) { - /* - * just a pixel move without change of residue - */ + lastMousePosition = null; + setToolTipText(null); + lastTooltip = null; + ap.alignFrame.setStatus(""); return; } - lastMouseColumn = column; - lastMouseSeq = seq; SequenceI sequence = av.getAlignment().getSequenceAt(seq); @@ -812,7 +948,7 @@ public class SeqPanel extends JPanel mouseOverSequence(sequence, column, pos); } - tooltipText.setLength(6); // Cuts the buffer back to + tooltipText.setLength(6); // "" SequenceGroup[] groups = av.getAlignment().findAllGroups(sequence); if (groups != null) @@ -863,16 +999,47 @@ public class SeqPanel extends JPanel String textString = tooltipText.toString(); if (lastTooltip == null || !lastTooltip.equals(textString)) { - String formattedTooltipText = JvSwingUtils.wrapTooltip(true, + formattedTooltipText = JvSwingUtils.wrapTooltip(true, textString); - setToolTipText(formattedTooltipText); + setToolTipText(formattedTooltipText); lastTooltip = textString; } } } + /** + * When the view is in wrapped mode, and the mouse is over an annotation row, + * shows the corresponding tooltip and status message (if any) + * + * @param pos + * @param column + */ + protected void mouseMovedOverAnnotation(MousePos pos) + { + final int column = pos.column; + final int rowIndex = pos.annotationIndex; + + if (column < 0 || !av.getWrapAlignment() || !av.isShowAnnotation() + || rowIndex < 0) + { + return; + } + AlignmentAnnotation[] anns = av.getAlignment().getAlignmentAnnotation(); + + String tooltip = AnnotationPanel.buildToolTip(anns[rowIndex], column, + anns); + setToolTipText(tooltip); + lastTooltip = tooltip; + + String msg = AnnotationPanel.getStatusMessage(av.getAlignment(), column, + anns[rowIndex]); + ap.alignFrame.setStatus(msg); + } + private Point lastp = null; + private JToolTip tempTip = new JLabel().createToolTip(); + /* * (non-Javadoc) * @@ -881,19 +1048,30 @@ public class SeqPanel extends JPanel @Override public Point getToolTipLocation(MouseEvent event) { - int x = event.getX(), w = getWidth(); - int wdth = (w - x < 200) ? -(w / 2) : 5; // switch sides when tooltip is too - // close to edge - Point p = lastp; - if (!event.isShiftDown() || p == null) + // BH 2018 + + if (tooltipText == null || tooltipText.length() <= 6) { - p = (tooltipText != null && tooltipText.length() > 6) - ? new Point(event.getX() + wdth, event.getY() - 20) - : null; + return null; } - /* - * TODO: try to modify position region is not obcured by tooltip - */ + + if (lastp != null && event.isShiftDown()) + { + return lastp; + } + + Point p = lastp; + int x = event.getX(); + int y = event.getY(); + int w = getWidth(); + + tempTip.setTipText(formattedTooltipText); + int tipWidth = (int) tempTip.getPreferredSize().getWidth(); + + // was x += (w - x < 200) ? -(w / 2) : 5; + x = (x + tipWidth < w ? x + 10 : w - tipWidth); + p = new Point(x, y + 20); // BH 2018 was - 20? + return lastp = p; } @@ -997,7 +1175,7 @@ public class SeqPanel extends JPanel text.append(" (").append(Integer.toString(residuePos)).append(")"); } - ap.alignFrame.statusBar.setText(text.toString()); + ap.alignFrame.setStatus(text.toString()); } /** @@ -1039,6 +1217,12 @@ public class SeqPanel extends JPanel @Override public void mouseDragged(MouseEvent evt) { + MousePos pos = findMousePosition(evt); + if (pos.isOverAnnotation() || pos.column == -1) + { + return; + } + if (mouseWheelPressed) { boolean inSplitFrame = ap.av.getCodingComplement() != null; @@ -1134,11 +1318,11 @@ public class SeqPanel extends JPanel if (!editingSeqs) { - doMouseDraggedDefineMode(evt); + dragStretchGroup(evt); return; } - int res = findColumn(evt); + int res = pos.column; if (res < 0) { @@ -1161,9 +1345,9 @@ public class SeqPanel extends JPanel } mouseDragging = true; - if ((scrollThread != null) && (scrollThread.isRunning())) + if (scrollThread != null) { - scrollThread.setEvent(evt); + scrollThread.setMousePosition(evt.getPoint()); } } @@ -1212,6 +1396,8 @@ public class SeqPanel extends JPanel } } + StringBuilder message = new StringBuilder(64); // for status bar + /* * make a name for the edit action, for * status bar message and Undo/Redo menu @@ -1219,10 +1405,12 @@ public class SeqPanel extends JPanel String label = null; if (groupEditing) { + message.append("Edit group:"); label = MessageManager.getString("action.edit_group"); } else { + message.append("Edit sequence: " + seq.getName()); label = seq.getName(); if (label.length() > 10) { @@ -1242,6 +1430,18 @@ public class SeqPanel extends JPanel editCommand = new EditCommand(label); } + if (insertGap) + { + message.append(" insert "); + } + else + { + message.append(" delete "); + } + + message.append(Math.abs(startres - editLastRes) + " gaps."); + ap.alignFrame.setStatus(message.toString()); + /* * is there a selection group containing the sequence being edited? * if so the boundary of the group is the limit of the edit @@ -1327,7 +1527,7 @@ public class SeqPanel extends JPanel * what was requested), by inspecting the edit commands added */ String msg = getEditStatusMessage(editCommand); - ap.alignFrame.statusBar.setText(msg == null ? " " : msg); + ap.alignFrame.setStatus(msg == null ? " " : msg); if (!success) { endEditing(); @@ -1718,10 +1918,10 @@ public class SeqPanel extends JPanel } /** - * DOCUMENT ME! + * On reentering the panel, stops any scrolling that was started on dragging + * out of the panel * * @param e - * DOCUMENT ME! */ @Override public void mouseEntered(MouseEvent e) @@ -1730,23 +1930,20 @@ public class SeqPanel extends JPanel { oldSeq = 0; } - - if ((scrollThread != null) && (scrollThread.isRunning())) - { - scrollThread.stopScrolling(); - scrollThread = null; - } + stopScrolling(); } /** - * DOCUMENT ME! + * On leaving the panel, if the mouse is being dragged, starts a thread to + * scroll it until the mouse is released (in unwrapped mode only) * * @param e - * DOCUMENT ME! */ @Override public void mouseExited(MouseEvent e) { + lastMousePosition = null; + ap.alignFrame.setStatus(" "); if (av.getWrapAlignment()) { return; @@ -1754,7 +1951,7 @@ public class SeqPanel extends JPanel if (mouseDragging && scrollThread == null) { - scrollThread = new ScrollThread(); + startScrolling(e.getPoint()); } } @@ -1767,7 +1964,12 @@ public class SeqPanel extends JPanel public void mouseClicked(MouseEvent evt) { SequenceGroup sg = null; - SequenceI sequence = av.getAlignment().getSequenceAt(findSeq(evt)); + MousePos pos = findMousePosition(evt); + if (pos.isOverAnnotation() || pos.seqIndex == -1 || pos.column == -1) + { + return; + } + if (evt.getClickCount() > 1) { sg = av.getSelectionGroup(); @@ -1777,12 +1979,13 @@ public class SeqPanel extends JPanel av.setSelectionGroup(null); } - int column = findColumn(evt); + int column = pos.column; /* * find features at the position (if not gapped), or straddling * the position (if at a gap) */ + SequenceI sequence = av.getAlignment().getSequenceAt(pos.seqIndex); List features = seqCanvas.getFeatureRenderer() .findFeaturesAtColumn(sequence, column + 1); @@ -1794,17 +1997,13 @@ public class SeqPanel extends JPanel SearchResultsI highlight = new SearchResults(); highlight.addResult(sequence, features.get(0).getBegin(), features .get(0).getEnd()); - seqCanvas.highlightSearchResults(highlight, false); + seqCanvas.highlightSearchResults(highlight, true); /* - * open the Amend Features dialog; clear highlighting afterwards, - * whether changes were made or not + * open the Amend Features dialog */ - List seqs = Collections.singletonList(sequence); - seqCanvas.getFeatureRenderer().amendFeatures(seqs, features, false, - ap); - av.setSearchResults(null); // clear highlighting - seqCanvas.repaint(); // draw new/amended features + new FeatureEditor(ap, Collections.singletonList(sequence), features, + false).showDialog(); } } } @@ -1849,32 +2048,22 @@ public class SeqPanel extends JPanel /** * DOCUMENT ME! * - * @param evt + * @param pos * DOCUMENT ME! */ - public void doMousePressedDefineMode(MouseEvent evt) + protected void doMousePressedDefineMode(MouseEvent evt, MousePos pos) { - final int res = findColumn(evt); - final int seq = findSeq(evt); - oldSeq = seq; - updateOverviewAndStructs = false; - - startWrapBlock = wrappedBlock; - - if (av.getWrapAlignment() && seq > av.getAlignment().getHeight()) + if (pos.isOverAnnotation() || pos.seqIndex == -1 || pos.column == -1) { - JvOptionPane.showInternalMessageDialog(Desktop.desktop, - MessageManager.getString( - "label.cannot_edit_annotations_in_wrapped_view"), - MessageManager.getString("label.wrapped_view_no_edit"), - JvOptionPane.WARNING_MESSAGE); return; } - if (seq < 0 || res < 0) - { - return; - } + final int res = pos.column; + final int seq = pos.seqIndex; + oldSeq = seq; + updateOverviewAndStructs = false; + + startWrapBlock = wrappedBlock; SequenceI sequence = av.getAlignment().getSequenceAt(seq); @@ -1896,26 +2085,26 @@ public class SeqPanel extends JPanel } } - if (evt.isPopupTrigger()) // Mac: mousePressed - { - showPopupMenu(evt); - return; - } - /* * defer right-mouse click handling to mouseReleased on Windows * (where isPopupTrigger() will answer true) * NB isRightMouseButton is also true for Cmd-click on Mac */ - if (SwingUtilities.isRightMouseButton(evt) && !Platform.isAMac()) + if (Platform.isWinRightButton(evt)) { return; } + if (evt.isPopupTrigger()) // Mac: mousePressed + { + showPopupMenu(evt, pos); + return; + } + if (av.cursorMode) { - seqCanvas.cursorX = findColumn(evt); - seqCanvas.cursorY = findSeq(evt); + seqCanvas.cursorX = res; + seqCanvas.cursorY = seq; seqCanvas.repaint(); return; } @@ -1973,15 +2162,14 @@ public class SeqPanel extends JPanel /** * Build and show a pop-up menu at the right-click mouse position - * + * * @param evt - * @param res - * @param sequences + * @param pos */ - void showPopupMenu(MouseEvent evt) + void showPopupMenu(MouseEvent evt, MousePos pos) { - final int column = findColumn(evt); - final int seq = findSeq(evt); + final int column = pos.column; + final int seq = pos.seqIndex; SequenceI sequence = av.getAlignment().getSequenceAt(seq); List features = ap.getFeatureRenderer() .findFeaturesAtColumn(sequence, column + 1); @@ -1999,7 +2187,8 @@ public class SeqPanel extends JPanel * true if this event is happening after a mouse drag (rather than a * mouse down) */ - public void doMouseReleasedDefineMode(MouseEvent evt, boolean afterDrag) + protected void doMouseReleasedDefineMode(MouseEvent evt, + boolean afterDrag) { if (stretchGroup == null) { @@ -2045,31 +2234,34 @@ public class SeqPanel extends JPanel } /** - * DOCUMENT ME! + * Resizes the borders of a selection group depending on the direction of + * mouse drag * * @param evt - * DOCUMENT ME! */ - public void doMouseDraggedDefineMode(MouseEvent evt) + protected void dragStretchGroup(MouseEvent evt) { - int res = findColumn(evt); - int y = findSeq(evt); - - if (wrappedBlock != startWrapBlock) + if (stretchGroup == null) { return; } - if (stretchGroup == null) + MousePos pos = findMousePosition(evt); + if (pos.isOverAnnotation() || pos.column == -1 || pos.seqIndex == -1) { return; } - if (res >= av.getAlignment().getWidth()) + int res = pos.column; + int y = pos.seqIndex; + + if (wrappedBlock != startWrapBlock) { - res = av.getAlignment().getWidth() - 1; + return; } + res = Math.min(res, av.getAlignment().getWidth()-1); + if (stretchGroup.getEndRes() == res) { // Edit end res position of selected group @@ -2154,9 +2346,9 @@ public class SeqPanel extends JPanel mouseDragging = true; - if ((scrollThread != null) && (scrollThread.isRunning())) + if (scrollThread != null) { - scrollThread.setEvent(evt); + scrollThread.setMousePosition(evt.getPoint()); } /* @@ -2186,86 +2378,133 @@ public class SeqPanel extends JPanel ap.alignFrame.setStatus(status.toString()); } - void scrollCanvas(MouseEvent evt) + /** + * Stops the scroll thread if it is running + */ + void stopScrolling() + { + if (scrollThread != null) + { + scrollThread.stopScrolling(); + scrollThread = null; + } + mouseDragging = false; + } + + /** + * Starts a thread to scroll the alignment, towards a given mouse position + * outside the panel bounds, unless the alignment is in wrapped mode + * + * @param mousePos + */ + void startScrolling(Point mousePos) { - if (evt == null) + /* + * set this.mouseDragging in case this was called from + * a drag in ScalePanel or AnnotationPanel + */ + mouseDragging = true; + if (!av.getWrapAlignment() && scrollThread == null) { - if ((scrollThread != null) && (scrollThread.isRunning())) + scrollThread = new ScrollThread(); + scrollThread.setMousePosition(mousePos); + if (Platform.isJS()) { - scrollThread.stopScrolling(); - scrollThread = null; + /* + * Javascript - run every 20ms until scrolling stopped + * or reaches the limit of scrollable alignment + */ + Timer t = new Timer(20, new ActionListener() + { + @Override + public void actionPerformed(ActionEvent e) + { + if (scrollThread != null) + { + // if (!scrollOnce() {t.stop();}) gives compiler error :-( + scrollThread.scrollOnce(); + } + } + }); + t.addActionListener(new ActionListener() + { + @Override + public void actionPerformed(ActionEvent e) + { + if (scrollThread == null) + { + // SeqPanel.stopScrolling called + t.stop(); + } + } + }); + t.start(); } - mouseDragging = false; } else { - if (scrollThread == null) - { - scrollThread = new ScrollThread(); - } - - mouseDragging = true; - scrollThread.setEvent(evt); + /* + * Java - run in a new thread + */ + scrollThread.start(); } - } - // this class allows scrolling off the bottom of the visible alignment + /** + * Performs scrolling of the visible alignment left, right, up or down, until + * scrolling is stopped by calling stopScrolling, mouse drag is ended, or the + * limit of the alignment is reached + */ class ScrollThread extends Thread { - MouseEvent evt; + private Point mousePos; - private volatile boolean threadRunning = true; + private volatile boolean keepRunning = true; + /** + * Constructor + */ public ScrollThread() { - start(); + setName("SeqPanel$ScrollThread"); } - public void setEvent(MouseEvent e) + /** + * Sets the position of the mouse that determines the direction of the + * scroll to perform. If this is called as the mouse moves, scrolling should + * respond accordingly. For example, if the mouse is dragged right, scroll + * right should start; if the drag continues down, scroll down should also + * happen. + * + * @param p + */ + public void setMousePosition(Point p) { - evt = e; + mousePos = p; } + /** + * Sets a flag that will cause the thread to exit + */ public void stopScrolling() { - threadRunning = false; - } - - public boolean isRunning() - { - return threadRunning; + keepRunning = false; } + /** + * Scrolls the alignment left or right, and/or up or down, depending on the + * last notified mouse position, until the limit of the alignment is + * reached, or a flag is set to stop the scroll + */ @Override public void run() { - while (threadRunning) + while (keepRunning) { - if (evt != null) + if (mousePos != null) { - if (mouseDragging && (evt.getY() < 0) - && (av.getRanges().getStartSeq() > 0)) - { - av.getRanges().scrollUp(true); - } - - if (mouseDragging && (evt.getY() >= getHeight()) && (av - .getAlignment().getHeight() > av.getRanges().getEndSeq())) - { - av.getRanges().scrollUp(false); - } - - if (mouseDragging && (evt.getX() < 0)) - { - av.getRanges().scrollRight(false); - } - else if (mouseDragging && (evt.getX() >= getWidth())) - { - av.getRanges().scrollRight(true); - } + keepRunning = scrollOnce(); } - try { Thread.sleep(20); @@ -2273,6 +2512,60 @@ public class SeqPanel extends JPanel { } } + SeqPanel.this.scrollThread = null; + } + + /** + * Scrolls + *
    + *
  • one row up, if the mouse is above the panel
  • + *
  • one row down, if the mouse is below the panel
  • + *
  • one column left, if the mouse is left of the panel
  • + *
  • one column right, if the mouse is right of the panel
  • + *
+ * Answers true if a scroll was performed, false if not - meaning either + * that the mouse position is within the panel, or the edge of the alignment + * has been reached. + */ + boolean scrollOnce() + { + /* + * quit after mouseUp ensures interrupt in JalviewJS + */ + if (!mouseDragging) + { + return false; + } + + boolean scrolled = false; + ViewportRanges ranges = SeqPanel.this.av.getRanges(); + + /* + * scroll up or down + */ + if (mousePos.y < 0) + { + // mouse is above this panel - try scroll up + scrolled = ranges.scrollUp(true); + } + else if (mousePos.y >= getHeight()) + { + // mouse is below this panel - try scroll down + scrolled = ranges.scrollUp(false); + } + + /* + * scroll left or right + */ + if (mousePos.x < 0) + { + scrolled |= ranges.scrollRight(false); + } + else if (mousePos.x >= getWidth()) + { + scrolled |= ranges.scrollRight(true); + } + return scrolled; } }