/* * Jalview - A Sequence Alignment Editor and Viewer * Copyright (C) 2005 AM Waterhouse, J Procter, G Barton, M Clamp, S Searle * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ package jalview.gui; import jalview.datamodel.*; import jalview.schemes.*; import java.awt.*; import java.awt.event.*; import javax.swing.*; /** * DOCUMENT ME! * * @author $author$ * @version $Revision$ */ public class SeqPanel extends JPanel implements MouseListener, MouseMotionListener, MouseWheelListener { /** DOCUMENT ME!! */ public SeqCanvas seqCanvas; /** DOCUMENT ME!! */ public AlignmentPanel ap; protected int lastres; protected int startseq; int startEdit = -1; int endEdit = -1; protected AlignViewport av; // if character is inserted or deleted, we will need to recalculate the conservation boolean seqEditOccurred = false; ScrollThread scrollThread = null; boolean mouseDragging = false; boolean editingSeqs = false; boolean groupEditing = false; ////////////////////////////////////////// /////Everything below this is for defining the boundary of the rubberband ////////////////////////////////////////// int oldSeq = -1; boolean changeEndSeq = false; boolean changeStartSeq = false; boolean changeEndRes = false; boolean changeStartRes = false; SequenceGroup stretchGroup = null; boolean remove = false; boolean mouseWheelPressed = false; /** * Creates a new SeqPanel object. * * @param avp DOCUMENT ME! * @param p DOCUMENT ME! */ public SeqPanel(AlignViewport avp, AlignmentPanel p) { ToolTipManager.sharedInstance().registerComponent(this); ToolTipManager.sharedInstance().setInitialDelay(0); ToolTipManager.sharedInstance().setDismissDelay(10000); this.av = avp; setBackground(Color.white); seqCanvas = new SeqCanvas(avp); setLayout(new BorderLayout()); add(seqCanvas, BorderLayout.CENTER); ap = p; if(!av.isDataset()) { addMouseMotionListener(this); addMouseListener(this); addMouseWheelListener(this); } } int startWrapBlock=-1; int wrappedBlock=-1; int findRes(MouseEvent evt) { int res = 0; int x = evt.getX(); if (av.wrapAlignment) { int hgap = av.charHeight; if (av.scaleAboveWrapped) hgap += av.charHeight; int cHeight = av.getAlignment().getHeight() * av.charHeight + hgap + seqCanvas.getAnnotationHeight(); int y = evt.getY(); y -= hgap; x -= seqCanvas.LABEL_WEST; int cwidth = seqCanvas.getWrappedCanvasWidth(this.getWidth()); wrappedBlock = y / cHeight; wrappedBlock += av.getStartRes() / cwidth; res = wrappedBlock * cwidth + x / av.getCharWidth(); } else { res = (x / av.getCharWidth()) + av.getStartRes(); } if(av.hasHiddenColumns) res = av.getColumnSelection().adjustForHiddenColumns(res); return res; } int findSeq(MouseEvent evt) { int seq = 0; int y = evt.getY(); if (av.wrapAlignment) { int hgap = av.charHeight; if (av.scaleAboveWrapped) hgap += av.charHeight; int cHeight = av.getAlignment().getHeight() * av.charHeight + hgap + seqCanvas.getAnnotationHeight(); y -= hgap; seq = ( (y % cHeight) / av.getCharHeight()); } else { seq = (y / av.getCharHeight()) + av.getStartSeq(); } return seq; } void endEditing() { startseq = -1; lastres = -1; seqEditOccurred = false; editingSeqs = false; groupEditing = false; } /** * DOCUMENT ME! * * @param evt DOCUMENT ME! */ public void mouseReleased(MouseEvent evt) { mouseDragging = false; if (!editingSeqs) { doMouseReleasedDefineMode(evt); return; } if (seqEditOccurred) { editOccurred(); } endEditing(); ap.repaint(); } /** * DOCUMENT ME! * * @param evt DOCUMENT ME! */ public void mousePressed(MouseEvent evt) { if (javax.swing.SwingUtilities.isMiddleMouseButton(evt)) { mouseWheelPressed = true; return; } if (evt.isShiftDown() || evt.isAltDown() || evt.isControlDown()) { if (evt.isAltDown() || evt.isControlDown()) { groupEditing = true; } editingSeqs = true; } else { doMousePressedDefineMode(evt); return; } ap.alignFrame.addHistoryItem(new HistoryItem("Edit Sequence", av.alignment, HistoryItem.EDIT)); int seq = findSeq(evt); int res = findRes(evt); if(seq<0 || res<0) return; if ((seq < av.getAlignment().getHeight()) && (res < av.getAlignment().getSequenceAt(seq).getLength())) { startseq = seq; lastres = res; } else { startseq = -1; lastres = -1; } startEdit = lastres; endEdit = lastres; return; } /** * DOCUMENT ME! * * @param evt DOCUMENT ME! */ public void mouseMoved(MouseEvent evt) { if (editingSeqs) { // This is because MacOSX creates a mouseMoved // If control is down, other platforms will not. mouseDragged(evt); } int res = findRes(evt); int seq = findSeq(evt); if(res<0 || seq<0 || seq >= av.getAlignment().getHeight()) return; SequenceI sequence = av.getAlignment().getSequenceAt(seq); if (res > sequence.getLength()) { return; } if(seqCanvas.pdbCanvas!=null && sequence==seqCanvas.pdbCanvas.sequence) { seqCanvas.pdbCanvas.highlightRes(sequence.findPosition(res)); } StringBuffer text = new StringBuffer("Sequence " + (seq + 1) + " ID: " + sequence.getName()); Object obj = null; if (av.alignment.isNucleotide()) { obj = ResidueProperties.nucleotideName.get(sequence.getCharAt(res) + ""); if(obj!=null) text.append(" Nucleotide: "); } else { obj = ResidueProperties.aa2Triplet.get(sequence.getCharAt(res) + ""); if(obj!=null) text.append(" Residue: "); } if (obj != null) { if (obj != "") { text.append( obj + " (" + av.getAlignment().getSequenceAt(seq).findPosition(res) + ")"); } } ap.alignFrame.statusBar.setText(text.toString()); // use aa to see if the mouse pointer is on a if (av.showSequenceFeatures) { SequenceFeature [] features = sequence.getDatasetSequence().getSequenceFeatures(); if(features!=null) { StringBuffer sbuffer = new StringBuffer(""); for (int i = 0; i < features.length; i++) { if ( (features[i].getBegin() <= sequence.findPosition(res)) && (features[i].getEnd() >= sequence.findPosition(res))) { if(!av.featuresDisplayed.containsKey(features[i].getType())) continue; if (features[i].getType().equals("disulfide bond")) { if (features[i].getBegin() == sequence.findPosition(res) || features[i].getEnd() == sequence.findPosition(res)) { if (sbuffer.length() > 6) sbuffer.append("
"); sbuffer.append("disulfide bond " + features[i].getBegin() + ":" + features[i].getEnd()); } } else { if (sbuffer.length() > 6) sbuffer.append("
"); if(features[i].featureGroup!=null) sbuffer.append(features[i].featureGroup+";"); sbuffer.append(features[i].getType()); if (features[i].getDescription() != null && !features[i].description.equals(features[i].getType())) sbuffer.append("; " + features[i].getDescription()); if (features[i].getStatus() != null && features[i].getStatus().length()>0) { sbuffer.append("; (" + features[i].getStatus() + ")"); } } } } sbuffer.append(""); if(sbuffer.length()==13) // setToolTipText(""); else setToolTipText(sbuffer.toString()); } else setToolTipText(""); } } /** * DOCUMENT ME! * * @param evt DOCUMENT ME! */ public void mouseDragged(MouseEvent evt) { if (!editingSeqs) { doMouseDraggedDefineMode(evt); return; } int res = findRes(evt); if (res < 0) { res = 0; } if ((lastres == -1) || (lastres == res)) { return; } boolean dragRight = true; if ((res < av.getAlignment().getWidth()) && (res < lastres)) { dragRight = false; } else if(av.hasHiddenColumns) { //Stop editing if the user has dragged beyond hiddenBoundary int lastCol = av.getColumnSelection().getHiddenRegionBoundary(lastres); if( lastCol < res) { if(lastres!=lastCol) { endEditing(); return; } } } if(!groupEditing && av.hasHiddenRows) { if(av.alignment.getSequenceAt(startseq).getHiddenSequences()!=null) { groupEditing = true; } } if (res != lastres) { SequenceI seq; // Group editing if (groupEditing) { SequenceGroup sg = av.getSelectionGroup(); if (av.hasHiddenRows) { //sg might be null as the user may only see 1 sequence if(sg==null) { sg = new SequenceGroup(); sg.addSequence(av.alignment.getSequenceAt(startseq), false); } SequenceGroup tmp = new SequenceGroup(); //Do any of the sequences have hidden associates? for (int s = 0; s < sg.getSize(); s++) { seq = sg.getSequenceAt(s); tmp.addSequence(seq, false); if (seq.getHiddenSequences()!=null) { for(int h=0; h 1) { res = lastres+1; } int lastCol = av.getColumnSelection(). getHiddenRegionBoundary(res); if(lastCol!=res) { for (blankColumn = lastCol; blankColumn > lastres; blankColumn--) { boolean blank = true; for (int s = 0; s < sg.getSize(); s++) { seq = sg.getSequenceAt(s); if (seq.getSequence().length() <= blankColumn) { continue; } if (!jalview.util.Comparison.isGap( seq.getSequence().charAt(blankColumn))) { blank = false; continue; } } if (blank) break; } if (blankColumn <= lastres) { endEditing(); return; } } else blankColumn = -1; } sg.setEndRes(sg.getEndRes() + (res - lastres)); } // drag to left else { /// Are we able to delete? // ie are all columns blank? for (int s = 0; s < sg.getSize(); s++) { seq = sg.getSequenceAt(s); for (int j = res; j < lastres; j++) { if (seq.getSequence().length() <= j) { continue; } if (!jalview.util.Comparison.isGap( seq.getSequence().charAt(j))) { // Not a gap, block edit not valid endEditing(); return; } } } if(res res) { deleteChar(res, seq); } } } } } else /////Editing a single sequence/////////// { seq = av.alignment.getSequenceAt(startseq); if ((res < av.getAlignment().getWidth()) && (res > lastres)) { // dragging to the right for (int j = lastres; j < res; j++) { insertChar(j, seq, j); } } else if ((res < av.getAlignment().getWidth()) && (res < lastres)) { // dragging to the left for (int j = lastres; j > res; j--) { if (jalview.util.Comparison.isGap( av.alignment.getSequenceAt(startseq) .getSequence().charAt(res))) { deleteChar(res, seq); } else { break; } } } } } mouseDragging = true; if(scrollThread!=null) scrollThread.setEvent(evt); endEdit = res; lastres = res; seqCanvas.repaint(); } /** * DOCUMENT ME! * * @param e DOCUMENT ME! */ public void mouseEntered(MouseEvent e) { if(oldSeq < 0) oldSeq = 0; if (scrollThread != null) { scrollThread.running = false; scrollThread = null; } } /** * DOCUMENT ME! * * @param e DOCUMENT ME! */ public void mouseExited(MouseEvent e) { if (av.getWrapAlignment()) { return; } if (mouseDragging) { scrollThread = new ScrollThread(); } } public void mouseClicked(MouseEvent evt) {} public void mouseWheelMoved(MouseWheelEvent e) { if (mouseWheelPressed) { Font font = av.getFont(); int fontSize = font.getSize(); if (e.getWheelRotation() > 0 && fontSize < 51) fontSize++; else if (fontSize > 1) fontSize--; av.setFont(new Font(font.getName(), font.getStyle(), fontSize)); ap.fontChanged(); } else { if (e.getWheelRotation() > 0) ap.scrollUp(false); else ap.scrollUp(true); } } /** * DOCUMENT ME! * * @param j DOCUMENT ME! * @param seq DOCUMENT ME! */ void insertChar(int j, SequenceI seq, int blankColumn) { if(av.hasHiddenColumns) { //Find the next gap before the end of the visible region boundary int lastCol = av.getColumnSelection().getHiddenRegionBoundary(j); if(lastCol != j) { if (!groupEditing || lastCol <= j) { blankColumn = lastCol; //If lastCol > j, theres a boundary after the gap insertion if (lastCol > j) { for (blankColumn = lastCol; blankColumn > j; blankColumn--) { if (jalview.util.Comparison.isGap(seq.getCharAt(blankColumn))) { //Theres a space, so break and insert the gap break; } } if (blankColumn <= j) { endEditing(); return; } } } // Editing with hidden regions only!! seq.deleteCharAt(blankColumn); } } seq.insertCharAt(j, av.getGapCharacter()); seqEditOccurred = true; } /** * DOCUMENT ME! * * @param j DOCUMENT ME! * @param seq DOCUMENT ME! */ public void deleteChar(int j, SequenceI seq) { if (av.hasHiddenColumns) { //Find the next gap before the end of the visible region boundary int lastCol = av.getColumnSelection().getHiddenRegionBoundary(j); //If lastCol > j, theres a boundary after the gap insertion if (lastCol > j) { seq.insertCharAt(lastCol, av.getGapCharacter()); } } seq.deleteCharAt(j); seqEditOccurred = true; seqCanvas.repaint(); } /** * DOCUMENT ME! * * @param i DOCUMENT ME! */ void editOccurred() { if (endEdit == startEdit) { ap.alignFrame.historyList.pop(); ap.alignFrame.updateEditMenuBar(); } av.firePropertyChange("alignment", null,av.getAlignment().getSequences()); } /** * DOCUMENT ME! * * @param evt DOCUMENT ME! */ public void doMousePressedDefineMode(MouseEvent evt) { int res = findRes(evt); int seq = findSeq(evt); oldSeq = seq; startWrapBlock=wrappedBlock; if(av.wrapAlignment && seq>av.alignment.getHeight()) { JOptionPane.showInternalMessageDialog(Desktop.desktop, "Cannot edit annotations in wrapped view.", "Wrapped view - no edit", JOptionPane.WARNING_MESSAGE); return; } if(seq<0 || res<0) return; SequenceI sequence = (Sequence) av.getAlignment().getSequenceAt(seq); if ((sequence == null) || (res > sequence.getLength())) { return; } stretchGroup = av.getSelectionGroup(); if (stretchGroup == null) { stretchGroup = av.alignment.findGroup(sequence); if ((stretchGroup != null) && (res > stretchGroup.getStartRes()) && (res < stretchGroup.getEndRes())) { av.setSelectionGroup(stretchGroup); } else { stretchGroup = null; } } else if (!stretchGroup.sequences.contains(sequence) || (stretchGroup.getStartRes() > res) || (stretchGroup.getEndRes() < res)) { stretchGroup = null; SequenceGroup[] allGroups = av.alignment.findAllGroups(sequence); if (allGroups != null) { for (int i = 0; i < allGroups.length; i++) { if ((allGroups[i].getStartRes() <= res) && (allGroups[i].getEndRes() >= res)) { stretchGroup = allGroups[i]; av.setSelectionGroup(stretchGroup); break; } } } } if (stretchGroup == null) { // define a new group here SequenceGroup sg = new SequenceGroup(); sg.setStartRes(res); sg.setEndRes(res); sg.addSequence(sequence, false); av.setSelectionGroup(sg); stretchGroup = sg; if (av.getConservationSelected()) { SliderPanel.setConservationSlider(ap, av.getGlobalColourScheme(), "Background"); } if (av.getAbovePIDThreshold()) { SliderPanel.setPIDSliderSource(ap, av.getGlobalColourScheme(), "Background"); } } else if (javax.swing.SwingUtilities.isRightMouseButton(evt)) { jalview.gui.PopupMenu pop = new jalview.gui.PopupMenu(ap, null); pop.show(this, evt.getX(), evt.getY()); // edit the properties of existing group } if ((stretchGroup != null) && (stretchGroup.getEndRes() == res)) { // Edit end res position of selected group changeEndRes = true; } else if ((stretchGroup != null) && (stretchGroup.getStartRes() == res)) { // Edit end res position of selected group changeStartRes = true; } stretchGroup.getWidth(); seqCanvas.repaint(); } /** * DOCUMENT ME! * * @param evt DOCUMENT ME! */ public void doMouseReleasedDefineMode(MouseEvent evt) { if (stretchGroup == null) { return; } if(stretchGroup.cs!=null) { if (stretchGroup.cs instanceof ClustalxColourScheme) { ( (ClustalxColourScheme) stretchGroup.cs).resetClustalX(stretchGroup. sequences, stretchGroup.getWidth()); } if (stretchGroup.cs.conservationApplied()) { SliderPanel.setConservationSlider(ap, stretchGroup.cs, stretchGroup.getName()); stretchGroup.recalcConservation(); } else { SliderPanel.setPIDSliderSource(ap, stretchGroup.cs, stretchGroup.getName()); } } changeEndRes = false; changeStartRes = false; stretchGroup = null; PaintRefresher.Refresh(av.alignment); } /** * DOCUMENT ME! * * @param evt DOCUMENT ME! */ public void doMouseDraggedDefineMode(MouseEvent evt) { int res = findRes(evt); int y = findSeq(evt); if(wrappedBlock!=startWrapBlock) return; if (stretchGroup == null) { return; } if(y > av.alignment.getHeight()) { y = av.alignment.getHeight() -1; } if (stretchGroup.getEndRes() == res) { // Edit end res position of selected group changeEndRes = true; } else if (stretchGroup.getStartRes() == res) { // Edit start res position of selected group changeStartRes = true; } if (res < av.getStartRes()) { res = av.getStartRes(); } if (changeEndRes) { if (res > (stretchGroup.getStartRes() - 1)) { stretchGroup.setEndRes(res); } } else if (changeStartRes) { if (res < (stretchGroup.getEndRes() + 1)) { stretchGroup.setStartRes(res); } } int dragDirection = 0; if (y > oldSeq) { dragDirection = 1; } else if (y < oldSeq) { dragDirection = -1; } while ((y != oldSeq) && (oldSeq > -1) && (y < av.alignment.getHeight())) { // This routine ensures we don't skip any sequences, as the // selection is quite slow. Sequence seq = (Sequence) av.getAlignment().getSequenceAt(oldSeq); oldSeq += dragDirection; if(oldSeq<0) break; Sequence nextSeq = (Sequence) av.getAlignment().getSequenceAt(oldSeq); if (stretchGroup.sequences.contains(nextSeq)) { stretchGroup.deleteSequence(seq, false); } else { if (seq != null) { stretchGroup.addSequence(seq, false); } stretchGroup.addSequence(nextSeq, false); } } if(oldSeq < 0) oldSeq = -1; mouseDragging = true; if (scrollThread != null) { scrollThread.setEvent(evt); } seqCanvas.repaint(); } // this class allows scrolling off the bottom of the visible alignment class ScrollThread extends Thread { MouseEvent evt; boolean running = false; public ScrollThread() { start(); } public void setEvent(MouseEvent e) { evt = e; } public void stopScrolling() { running = false; } public void run() { running = true; while (running) { if (evt != null) { if (mouseDragging && (evt.getY() < 0) && (av.getStartSeq() > 0)) { running = ap.scrollUp(true); } if (mouseDragging && (evt.getY() >= getHeight()) && (av.alignment.getHeight() > av.getEndSeq())) { running = ap.scrollUp(false); } if (mouseDragging && (evt.getX() < 0)) { running = ap.scrollRight(true); } else if (mouseDragging && (evt.getX() >= getWidth())) { running = ap.scrollRight(false); } } try { Thread.sleep(20); } catch (Exception ex) { } } } } }