/* VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases. Copyright (C) 2008 Kevin Darty, Alain Denise and Yann Ponty. electronic mail : Yann.Ponty@lri.fr paper mail : LRI, bat 490 University Paris-Sud 91405 Orsay Cedex France This file is part of VARNA version 3.1. VARNA version 3.1 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 3 of the License, or (at your option) any later version. VARNA version 3.1 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 VARNA version 3.1. If not, see http://www.gnu.org/licenses. */ package fr.orsay.lri.varna.controlers; import java.awt.Component; import java.awt.Point; import java.awt.Rectangle; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.awt.geom.Point2D; import java.util.ArrayList; import java.util.Collection; import java.util.Vector; import javax.swing.JMenu; import javax.swing.JMenuItem; import javax.swing.event.PopupMenuEvent; import javax.swing.event.PopupMenuListener; import fr.orsay.lri.varna.VARNAPanel; import fr.orsay.lri.varna.exceptions.ExceptionNAViewAlgorithm; import fr.orsay.lri.varna.models.annotations.TextAnnotation; import fr.orsay.lri.varna.models.rna.ModeleBase; import fr.orsay.lri.varna.models.rna.ModeleBaseNucleotide; import fr.orsay.lri.varna.models.rna.ModeleBasesComparison; import fr.orsay.lri.varna.models.rna.ModeleBP; import fr.orsay.lri.varna.models.rna.RNA; /** * Controller of the mouse click * * @author darty * */ public class ControleurClicMovement implements MouseListener, MouseMotionListener, PopupMenuListener { private VARNAPanel _vp; private boolean _presenceMenuSelection; private JMenu _submenuSelection; public Point _spawnPoint; public Point _initialPoint; public Point _prevPoint; public Point _currentPoint; public static final double MIN_SELECTION_DISTANCE = 40.0; public static final double HYSTERESIS_DISTANCE = 10.0; private ModeleBase _selectedBase = null; public enum MouseStates { NONE, MOVE_ELEMENT, MOVE_OR_SELECT_ELEMENT, SELECT_ELEMENT, SELECT_REGION_OR_UNSELECT, SELECT_REGION, CREATE_BP, POPUP_MENU, MOVE_ANNOTATION, }; private MouseStates _currentState = MouseStates.NONE; public ControleurClicMovement(VARNAPanel _vuep) { _vp = _vuep; _vp.getPopup().addPopupMenuListener(this); _presenceMenuSelection = false; } public void mouseClicked(MouseEvent arg0) { } public void mouseEntered(MouseEvent arg0) { } public void mouseExited(MouseEvent arg0) { } public void mousePressed(MouseEvent arg0) { _vp.requestFocus(); boolean button1 = (arg0.getButton() == MouseEvent.BUTTON1); boolean button2 = (arg0.getButton() == MouseEvent.BUTTON2); boolean button3 = (arg0.getButton() == MouseEvent.BUTTON3); boolean shift = arg0.isShiftDown(); boolean ctrl = arg0.isControlDown(); boolean alt = arg0.isAltDown(); _vp.removeSelectedAnnotation(); if (button1 && !ctrl && !alt && !shift) { if (_vp.isModifiable()) { _currentState = MouseStates.MOVE_OR_SELECT_ELEMENT; if (_vp.getRealCoords() != null && _vp.getRealCoords().length != 0 && _vp.getRNA().get_listeBases().size() != 0) { _selectedBase = getNearestBase(arg0,false,false); TextAnnotation selectedAnnotation = this.getNearestAnnotation(arg0); _initialPoint = new Point(arg0.getX(),arg0.getY()); _currentPoint = new Point(_initialPoint); _prevPoint = new Point(_initialPoint); if (_selectedBase != null) { if (_vp.getRNA().get_drawMode() == RNA.DRAW_MODE_RADIATE) { _vp.highlightSelectedBase(_selectedBase); } else { if (!_vp.getSelectionIndices().contains(_selectedBase.getIndex())) { _vp.highlightSelectedBase(_selectedBase); } else { // Otherwise, keep current selection as it is and move it } } } else { if (selectedAnnotation != null) { _currentState = MouseStates.MOVE_ANNOTATION; _vp.set_selectedAnnotation(selectedAnnotation); _vp.highlightSelectedAnnotation(); } else { _vp.clearSelection(); _selectedBase = null; _currentState = MouseStates.SELECT_REGION_OR_UNSELECT; _initialPoint = new Point(arg0.getX(),arg0.getY()); _prevPoint = new Point(_initialPoint); _currentPoint = new Point(_initialPoint); } } } } } else if (button1 && ctrl && !alt && !shift) { _selectedBase = getNearestBase(arg0,false,false); if (_selectedBase != null) { _vp.clearSelection(); _currentState = MouseStates.CREATE_BP; _vp.highlightSelectedBase(_selectedBase); _vp.setOriginLink(_vp.logicToPanel(_selectedBase.getCoords())); _initialPoint = new Point(arg0.getX(),arg0.getY()); _currentPoint = new Point(_initialPoint); } } else if (button1 && !ctrl && !alt && shift) { _currentState = MouseStates.SELECT_ELEMENT; _initialPoint = new Point(arg0.getX(),arg0.getY()); _currentPoint = new Point(_initialPoint); } else if (button3) { _currentState = MouseStates.POPUP_MENU; if (_presenceMenuSelection) { _vp.getPopupMenu().removeSelectionMenu(); } if ((_vp.getRealCoords() != null) && _vp.getRNA().get_listeBases().size() != 0) { updateNearestBase(arg0); // on insere dans le menu les nouvelles options addMenu(arg0); if (_vp.get_selectedAnnotation() != null) _vp.highlightSelectedAnnotation(); } // affichage du popup menu if (_vp.getRNA().get_drawMode() == RNA.DRAW_MODE_LINEAR) { _vp.getPopup().get_rotation().setEnabled(false); } else { _vp.getPopup().get_rotation().setEnabled(true); } _vp.getPopup().updateDialog(); _vp.getPopup().show(_vp, arg0.getX(), arg0.getY()); } _vp.repaint(); } public void mouseDragged(MouseEvent me) { if ((_currentState == MouseStates.MOVE_OR_SELECT_ELEMENT)||(_currentState == MouseStates.MOVE_ELEMENT)) { _vp.lockScrolling(); _currentState = MouseStates.MOVE_ELEMENT; // si on deplace la souris et qu'une base est selectionnée if (_selectedBase != null) { if (_vp.getRNA().get_drawMode() == RNA.DRAW_MODE_RADIATE) { _vp.highlightSelectedStem(_selectedBase); // dans le cas radiale on deplace une helice _vp.getVARNAUI().UIMoveHelixAtom(_selectedBase.getIndex(), _vp.panelToLogicPoint(new Point2D.Double(me.getX(), me.getY()))); } else { // dans le cas circulaire naview ou line on deplace une base _currentPoint = new Point(me.getX(), me.getY()); moveSelection(_prevPoint,_currentPoint); _prevPoint = new Point(_currentPoint); } _vp.repaint(); } } else if (_currentState == MouseStates.MOVE_ANNOTATION) { if (_vp.get_selectedAnnotation()!=null) { Point2D.Double p = _vp.panelToLogicPoint(new Point2D.Double(me.getX(), me.getY())); _vp.get_selectedAnnotation().setAncrage(p.x,p.y); _vp.repaint(); } } else if ((_currentState == MouseStates.SELECT_ELEMENT)||(_currentState == MouseStates.SELECT_REGION_OR_UNSELECT)) { if (_initialPoint.distance(me.getX(),me.getY())>HYSTERESIS_DISTANCE) _currentState = MouseStates.SELECT_REGION; } else if (_currentState == MouseStates.SELECT_REGION) { _currentPoint = new Point(me.getX(),me.getY()); int minx = Math.min(_currentPoint.x, _initialPoint.x); int miny = Math.min(_currentPoint.y, _initialPoint.y); int maxx = Math.max(_currentPoint.x, _initialPoint.x); int maxy = Math.max(_currentPoint.y, _initialPoint.y); _vp.setSelectionRectangle(new Rectangle(minx,miny,maxx-minx,maxy-miny)); } else if (_currentState == MouseStates.CREATE_BP) { if (_initialPoint.distance(me.getX(),me.getY())>HYSTERESIS_DISTANCE) { ModeleBase newSelectedBase = getNearestBase(me,false,false); _vp.setHoverBase(newSelectedBase); if (newSelectedBase==null) { _vp.setDestinationLink(new Point2D.Double(me.getX(),me.getY())); _vp.clearSelection(); _vp.addToSelection(_selectedBase.getIndex()); } else { ModeleBase mborig = _selectedBase; _vp.clearSelection(); _vp.addToSelection(newSelectedBase.getIndex()); _vp.addToSelection(mborig.getIndex()); _vp.setDestinationLink(_vp.logicToPanel(newSelectedBase.getCoords())); } _vp.repaint(); } } } public void mouseReleased(MouseEvent arg0) { if (arg0.getButton() == MouseEvent.BUTTON1) { if (_currentState == MouseStates.MOVE_ELEMENT) { _vp.clearSelection(); _selectedBase = null; _vp.unlockScrolling(); _vp.removeSelectedAnnotation(); } else if (_currentState == MouseStates.SELECT_REGION_OR_UNSELECT) { _vp.clearSelection(); _selectedBase = null; _vp.removeSelectedAnnotation(); } else if (_currentState == MouseStates.SELECT_ELEMENT) { if (_vp.getRealCoords() != null && _vp.getRealCoords().length != 0 && _vp.getRNA().get_listeBases().size() != 0) { int selectedIndex = getNearestBaseIndex(arg0,false,false); if (selectedIndex !=-1) { _vp.toggleSelection(selectedIndex); } } _selectedBase = null; } else if (_currentState == MouseStates.SELECT_REGION) { _vp.removeSelectionRectangle(); } else if (_currentState == MouseStates.CREATE_BP) { if (_initialPoint.distance(arg0.getX(),arg0.getY())>HYSTERESIS_DISTANCE) { int selectedIndex = getNearestBaseIndex(arg0,false,false); if (selectedIndex>=0) { ModeleBase mb = getNearestBase(arg0,false,false); ModeleBase mborig = _selectedBase; ModeleBP msbp = new ModeleBP(mb,mborig); if (mb!=mborig) { _vp.getVARNAUI().UIAddBP(mb.getIndex(),mborig.getIndex(),msbp); } } } _vp.removeLink(); _vp.clearSelection(); _vp.repaint(); } } _vp.removeSelectedAnnotation(); _currentState = MouseStates.NONE; _vp.repaint(); } private void addMenu(MouseEvent arg0) { // creation du menu _submenuSelection = new JMenu("Selection"); addCurrent(); // ajout des option sur base addMenuBase(); // ajout des option sur paire de base if (_vp.getRNA().get_listeBases().get(_vp.getNearestBase()) .getElementStructure() != -1) { addMenuBasePair(); } // detection renflement detectBulge(); // detection 3' detect3Prime(); // detection 5' detect5Prime(); // detection boucle detectLoop(); // detection d'helice detectHelix(); // detection tige detectStem(); // Ajout de toutes bases addAllBase(); // detection d'annotation detectAnnotation(arg0); _vp.getPopup().addSelectionMenu(_submenuSelection); _presenceMenuSelection = true; } private void detectAnnotation(MouseEvent arg0) { if (_vp.getListeAnnotations().size() != 0) { double dist = Double.MAX_VALUE; double d2; Point2D.Double position; for (TextAnnotation textAnnot : _vp.getListeAnnotations()) { // calcul de la distance position = textAnnot.getCenterPosition(); position = _vp.transformCoord(position); d2 = Math.sqrt(Math.pow((position.x - arg0.getX()), 2) + Math.pow((position.y - arg0.getY()), 2)); // si la valeur est inferieur au minimum actuel if (dist > d2) { _vp.set_selectedAnnotation(textAnnot); dist = d2; } } _submenuSelection.addSeparator(); _vp.getPopup().addAnnotationMenu(_submenuSelection,true); } } private void detectBulge() { int indiceB = _vp.getNearestBase(); ArrayList<Integer> indices = _vp.getRNA().findBulge(indiceB); if ((indices.size() > 0) && (_vp.getRNA().getHelixCountOnLoop(_vp.getNearestBase()) == 2)) { JMenu submenuBulge = new JMenu("Bulge"); submenuBulge.addChangeListener(new ControleurSelectionHighlight( new Vector<Integer>(indices), _vp, submenuBulge)); submenuBulge.setActionCommand("bulge"); if (!_vp.isModifiable()) submenuBulge.setEnabled(false); _vp.getPopupMenu().addColorOptions(submenuBulge); _submenuSelection.add(submenuBulge); } } private void detectHelix() { int indiceH = _vp.getNearestBase(); ArrayList<Integer> indices = _vp.getRNA().findHelix(indiceH); if (indices.size() != 0) { // ajout menu helice JMenu submenuHelix = new JMenu("Helix"); submenuHelix.addChangeListener(new ControleurSelectionHighlight( new Vector<Integer>(indices), _vp, submenuHelix)); submenuHelix.setActionCommand("helix"); if (!_vp.isModifiable()) submenuHelix.setEnabled(false); _vp.getPopupMenu().addColorOptions(submenuHelix); submenuHelix.addSeparator(); _vp.getPopupMenu().addAnnotationMenu(submenuHelix); _submenuSelection.add(submenuHelix); } } private void detectStem() { int indiceS = _vp.getNearestBase(); ArrayList<Integer> indices = _vp.getRNA().findStem(indiceS); if (indices.size() > 0) { JMenu submenuStem = new JMenu("Stem"); submenuStem.addChangeListener(new ControleurSelectionHighlight( new Vector<Integer>(indices), _vp, submenuStem)); submenuStem.setActionCommand("stem"); if (!_vp.isModifiable()) submenuStem.setEnabled(false); _vp.getPopupMenu().addColorOptions(submenuStem); _submenuSelection.add(submenuStem); } } private void detect3Prime() { // detection 3' int indice3 = _vp.getNearestBase(); ArrayList<Integer> indices = _vp.getRNA().find3Prime(indice3); if (indices.size() != 0) { JMenu submenu3Prime = new JMenu("3'"); submenu3Prime.addChangeListener(new ControleurSelectionHighlight( new Vector<Integer>(indices), _vp, submenu3Prime)); submenu3Prime.setActionCommand("3'"); if (!_vp.isModifiable()) submenu3Prime.setEnabled(false); _vp.getPopupMenu().addColorOptions(submenu3Prime); _submenuSelection.add(submenu3Prime); } } private void detect5Prime() { int indice5 = _vp.getNearestBase(); ArrayList<Integer> indices = _vp.getRNA().find5Prime(indice5); if (indices.size() != 0) { JMenu submenu5Prime = new JMenu("5'"); submenu5Prime.addChangeListener(new ControleurSelectionHighlight( new Vector<Integer>(indices), _vp, submenu5Prime)); submenu5Prime.setActionCommand("5'"); if (!_vp.isModifiable()) submenu5Prime.setEnabled(false); _vp.getPopupMenu().addColorOptions(submenu5Prime); _submenuSelection.add(submenu5Prime); } } private void detectLoop() { int indexL = _vp.getNearestBase(); if (_vp.getRNA().get_listeBases().get(indexL).getElementStructure() == -1) { ArrayList<Integer> listLoop = _vp.getRNA().findLoop(indexL); JMenu submenuLoop = new JMenu("Loop"); submenuLoop.addChangeListener(new ControleurSelectionHighlight( listLoop, _vp, submenuLoop)); submenuLoop.setActionCommand("loop1"); if (!_vp.isModifiable()) submenuLoop.setEnabled(false); _vp.getPopupMenu().addColorOptions(submenuLoop); submenuLoop.addSeparator(); _vp.getPopupMenu().addAnnotationMenu(submenuLoop); _submenuSelection.add(submenuLoop); } else { ArrayList<Integer> listLoop1 = _vp.getRNA().findLoopForward(indexL); if (listLoop1.size() > 0) { JMenu submenuLoop1 = new JMenu("Forward loop"); submenuLoop1 .addChangeListener(new ControleurSelectionHighlight( listLoop1, _vp, submenuLoop1)); submenuLoop1.setActionCommand("loop1"); if (!_vp.isModifiable()) submenuLoop1.setEnabled(false); _vp.getPopupMenu().addColorOptions(submenuLoop1); submenuLoop1.addSeparator(); _vp.getPopupMenu().addAnnotationMenu(submenuLoop1); _submenuSelection.add(submenuLoop1); } ArrayList<Integer> listLoop2 = _vp.getRNA() .findLoopBackward(indexL); if (listLoop2.size() > 0) { JMenu submenuLoop2 = new JMenu("Backward loop"); submenuLoop2 .addChangeListener(new ControleurSelectionHighlight( listLoop2, _vp, submenuLoop2)); submenuLoop2.setActionCommand("loop2"); if (!_vp.isModifiable()) submenuLoop2.setEnabled(false); _vp.getPopupMenu().addColorOptions(submenuLoop2); submenuLoop2.addSeparator(); _vp.getPopupMenu().addAnnotationMenu(submenuLoop2); _submenuSelection.add(submenuLoop2); } } } private void addCurrent() { Collection<? extends ModeleBase> mbs = _vp.getSelection().getBases(); if (mbs.size()>0) { JMenu submenuAll = new JMenu("Current"); submenuAll.addChangeListener(new ControleurSelectionHighlight( mbs, _vp, submenuAll)); submenuAll.setActionCommand("current"); if (!_vp.isModifiable()) submenuAll.setEnabled(false); _vp.getPopupMenu().addColorOptions(submenuAll); _submenuSelection.add(submenuAll); } } private void addMenuBase() { JMenu submenuBase = new JMenu(); ModeleBase mb = _vp.getRNA().get_listeBases().get(_vp.getNearestBase()); if (mb instanceof ModeleBasesComparison) { submenuBase.setText("Base #" + (mb.getBaseNumber()) + ":" + ((ModeleBasesComparison) mb).getBases()); } else { submenuBase.setText("Base #" + (mb.getBaseNumber()) + ":" + ((ModeleBaseNucleotide) mb).get_c()); } submenuBase.addChangeListener(new ControleurSelectionHighlight(mb .getIndex(), _vp, submenuBase)); submenuBase.setActionCommand("base"); // option disponible seulement en mode modifiable if (!_vp.isModifiable()) submenuBase.setEnabled(false); JMenuItem baseChar = new JMenuItem("Edit base"); baseChar.setActionCommand("baseChar"); baseChar.addActionListener(_vp.getPopupMenu().get_controleurMenu()); submenuBase.add(baseChar); _vp.getPopupMenu().addColorOptions(submenuBase); submenuBase.addSeparator(); _vp.getPopupMenu().addAnnotationMenu(submenuBase); _submenuSelection.add(submenuBase); } private void addAllBase() { ArrayList<Integer> indices = _vp.getRNA().findAll(); JMenu submenuAll = new JMenu("All"); submenuAll.addChangeListener(new ControleurSelectionHighlight( new Vector<Integer>(indices), _vp, submenuAll)); submenuAll.setActionCommand("all"); if (!_vp.isModifiable()) submenuAll.setEnabled(false); _vp.getPopupMenu().addColorOptions(submenuAll); _submenuSelection.add(submenuAll); } private void addMenuBasePair() { int indiceBP = _vp.getNearestBase(); ArrayList<Integer> indices = _vp.getRNA().findPair(indiceBP); ModeleBase base = _vp.getRNA() .get_listeBases().get(_vp.getNearestBase()); if (base.getElementStructure() != -1) { JMenu submenuBasePair = new JMenu(); ModeleBase partner = _vp .getRNA().get_listeBases().get( base.getElementStructure()); submenuBasePair .addChangeListener(new ControleurSelectionHighlight( indices, _vp, submenuBasePair)); submenuBasePair.setText("Base pair #(" + (Math.min(base.getBaseNumber(), partner .getBaseNumber())) + "," + (Math.max(base.getBaseNumber(), partner .getBaseNumber())) + ")"); submenuBasePair.setActionCommand("bp"); // option disponible seulement en mode modifiable if (!_vp.isModifiable()) submenuBasePair.setEnabled(false); JMenuItem basepair = new JMenuItem("Edit BP"); basepair.setActionCommand("basepair"); basepair.addActionListener(_vp.getPopupMenu() .get_controleurMenu()); _vp.getPopupMenu().addColorOptions(submenuBasePair); Component[] comps = submenuBasePair.getMenuComponents(); int offset = -1; for (int i = 0; i < comps.length; i++) { Component c = comps[i]; if (c instanceof JMenuItem) { JMenuItem jmi = (JMenuItem) c; if (jmi.getActionCommand().contains(",BPColor")) { offset = i; } } } if (offset != -1) { submenuBasePair.insert(basepair, offset); } else { submenuBasePair.add(basepair); } _submenuSelection.add(submenuBasePair); } } private ModeleBase getNearestBase(MouseEvent arg0, boolean always, boolean onlyPaired) { int i = getNearestBaseIndex(arg0, always,onlyPaired); if (i==-1) return null; return _vp.getRNA().get_listeBases().get(i); } private ModeleBase getNearestBase(MouseEvent arg0) { return getNearestBase(arg0, false ,false); } private int getNearestBaseIndex(MouseEvent arg0, boolean always, boolean onlyPaired) { double d2, dist = Double.MAX_VALUE; int mb = -1; for (int i = 0; i < _vp.getRealCoords().length; i++) { if (!onlyPaired || (_vp.getRNA().get_listeBases().get(i).getElementStructure()!=-1)) {d2 = Math.sqrt(Math .pow((_vp.getRealCoords()[i].x - arg0.getX()), 2) + Math.pow((_vp.getRealCoords()[i].y - arg0.getY()), 2)); if ((dist > d2) && ((d2<_vp.getScaleFactor()*MIN_SELECTION_DISTANCE) || always)) { dist = d2; mb = i; } } } return mb; } private void updateNearestBase(MouseEvent arg0) { int i = getNearestBaseIndex(arg0,true,false); if (i!=-1) _vp.setNearestBase(i); } private TextAnnotation getNearestAnnotation(MouseEvent arg0) { TextAnnotation t = null; if (_vp.getListeAnnotations().size() != 0) { double dist = Double.MAX_VALUE; double d2; Point2D.Double position; for (TextAnnotation textAnnot : _vp.getListeAnnotations()) { // calcul de la distance position = textAnnot.getCenterPosition(); position = _vp.transformCoord(position); d2 = Math.sqrt(Math.pow((position.x - arg0.getX()), 2) + Math.pow((position.y - arg0.getY()), 2)); // si la valeur est inferieur au minimum actuel if ((dist > d2)&& (d2<_vp.getScaleFactor()*MIN_SELECTION_DISTANCE)) { t = textAnnot; dist = d2; } } } return t; } public void mouseMoved(MouseEvent arg0) { _selectedBase = getNearestBase(arg0); TextAnnotation selectedAnnotation = getNearestAnnotation(arg0); _vp.setHoverBase(_selectedBase); if (_selectedBase != null) { } else if (selectedAnnotation!=null) { _vp.set_selectedAnnotation(selectedAnnotation); _vp.highlightSelectedAnnotation(); _vp.repaint(); } _vp.setLastSelectedPosition(new Point2D.Double(arg0.getX(),arg0.getY())); } private void moveSelection(Point prev, Point cur) { Point2D.Double p1 = _vp.panelToLogicPoint(new Point2D.Double(prev.x,prev.y)); Point2D.Double p2 = _vp.panelToLogicPoint(new Point2D.Double(cur.x,cur.y)); double dx = (p2.x - p1.x); double dy = (p2.y - p1.y); if (_vp.isModifiable()) { double ndx = dx; double ndy = dy; if (_vp.getRNA().get_drawMode() == RNA.DRAW_MODE_LINEAR) { ndy=0.0; } _vp.getVARNAUI().UIShiftBaseCoord(_vp.getSelectionIndices(), ndx, ndy); _vp.fireLayoutChanged(); } } public void popupMenuCanceled(PopupMenuEvent arg0) { } public void popupMenuWillBecomeInvisible(PopupMenuEvent arg0) { _vp.resetAnnotationHighlight(); _selectedBase = null; } public void popupMenuWillBecomeVisible(PopupMenuEvent arg0) { } }