/* * EuroCarbDB, a framework for carbohydrate bioinformatics * * Copyright (c) 2006-2009, Eurocarb project, or third-party contributors as * indicated by the @author tags or express copyright attribution * statements applied by the authors. * * This copyrighted material is made available to anyone wishing to use, modify, * copy, or redistribute it subject to the terms and conditions of the GNU * Lesser General Public License, as published by the Free Software Foundation. * A copy of this license accompanies this distribution in the file LICENSE.txt. * * 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 Lesser General Public License * for more details. * * Last commit: $Rev: 1210 $ by $Author: glycoslave $ on $Date:: 2009-06-12 #$ */ /** @author Alessio Ceroni (a.ceroni@imperial.ac.uk) */ package org.eurocarbdb.application.glycanbuilder; import java.util.*; import java.text.*; import java.io.*; import java.awt.*; import java.awt.geom.*; import java.awt.event.*; import java.awt.datatransfer.*; import java.awt.image.*; import java.awt.print.*; import javax.swing.*; class ResidueSelector extends JComponent implements MouseListener, MouseMotionListener { // Classes public interface SelectionChangeListener { public void selectionChanged(SelectionChangeEvent e); } public static class SelectionChangeEvent { private ResidueSelector src; public SelectionChangeEvent(ResidueSelector _src) { src = _src; } public ResidueSelector getSource() { return src; } } // ----------- protected static final long serialVersionUID = 0L; // singletons protected JScrollPane theScrollPane = null; // document protected Glycan theStructure = null; protected Collection<Residue> active_residues = null; // selection protected boolean allow_multiple_selection; protected Residue current_residue; protected HashSet<Residue> selected_residues; // painting protected GlycanRenderer theGlycanRenderer; protected Rectangle structure_bbox; protected BBoxManager theBBoxManager; protected PositionManager thePosManager; // events protected Point mouse_start_point = null; protected Point mouse_end_point = null; // protected Vector<SelectionChangeListener> listeners; //--------- // construction public ResidueSelector(Glycan structure, Collection<Residue> actives, boolean multiple_sel) { // init theStructure = structure; active_residues = actives; allow_multiple_selection = multiple_sel; // current_residue = null; selected_residues = new HashSet<Residue>(); theGlycanRenderer = new GlycanRenderer(); thePosManager = new PositionManager(); theBBoxManager = new BBoxManager(); structure_bbox = null; listeners = new Vector<SelectionChangeListener>(); // set the canvas this.setOpaque(true); this.setBackground(Color.white); // add mouse events addMouseMotionListener( this ); addMouseListener( this ); } public GlycanRenderer getGlycanRenderer() { return theGlycanRenderer; } public void setGlycanRenderer(GlycanRenderer r) { theGlycanRenderer = r; } public void setScrollPane(JScrollPane sp) { theScrollPane = sp; } //------------------- // JComponent public Dimension getPreferredSize() { if( structure_bbox==null ) structure_bbox = theGlycanRenderer.computeBoundingBoxes(new Union<Glycan>(theStructure),false,true,thePosManager,theBBoxManager); return theGlycanRenderer.computeSize(structure_bbox); } public Dimension getMinimumSize() { return new Dimension(0,0); } //------------------- // painting protected void paintComponent(Graphics g) { if (isOpaque()) { //paint background g.setColor(getBackground()); g.fillRect(0, 0, getWidth(), getHeight()); } // prepare graphic object Graphics2D g2d = (Graphics2D)g.create(); g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); // set clipping area Rectangle clipRect = new Rectangle(); g.getClipBounds(clipRect); if( mouse_start_point!=null && mouse_end_point!=null ) { g2d.setColor(Color.black); g2d.draw(Geometry.makeRectangle(mouse_start_point,mouse_end_point)); } // draw structure_bbox = theGlycanRenderer.computeBoundingBoxes(new Union<Glycan>(theStructure),false,true,thePosManager,theBBoxManager); theGlycanRenderer.paint(g2d,theStructure,selected_residues,null,active_residues,false,true,thePosManager,theBBoxManager); revalidate(); // dispose graphic object g2d.dispose(); } //--------------- // selection public Residue getResidueAtPoint(Point p) { Residue ret = getResidueAtPoint(theStructure.getRoot(),p); if( ret!=null ) return ret; ret = getResidueAtPoint(theStructure.getBracket(),p); if( ret!=null ) return ret; return null; } public Residue getResidueAtPoint(Residue r, Point p) { if( r==null ) return null; Rectangle cur_bbox = theBBoxManager.getCurrent(r); if( cur_bbox==null ) return null; if( cur_bbox.contains(p) ) return r; for( Linkage l : r.getChildrenLinkages() ) { Residue ret = getResidueAtPoint(l.getChildResidue(),p); if( ret!=null ) return ret; } return null; } public boolean hasSelection() { return (selected_residues.size()>0); } public void resetSelection() { selected_residues.clear(); current_residue = null; fireUpdatedSelection(); } public boolean hasCurrentSelection() { return (current_residue!=null ); } public Residue getCurrentSelection() { return current_residue; } public boolean hasCurrentResidue() { return (current_residue!=null); } public Residue getCurrentResidue() { return current_residue; } private void setCurrentResidue(Residue node) { if( active_residues!=null && !active_residues.contains(node)) node = null; if( node!=null ) selected_residues.add(node); current_residue = node; fireUpdatedSelection(); } public boolean isSelected(Residue node) { if( node==null ) return false; return selected_residues.contains(node); } public boolean hasSelectedResidues() { return !selected_residues.isEmpty(); } public Collection<Residue> getSelectedResiduesList() { return selected_residues; } public Residue[] getSelectedResidues() { return (Residue[])selected_residues.toArray(new Residue[0]); } public void setSelection(Collection<Residue> nodes) { if( active_residues!=null ) nodes = new Union<Residue>(active_residues).intersect(nodes); if( nodes==null || nodes.isEmpty() ) { resetSelection(); } else if( nodes.size()==1 ) { selected_residues.clear(); current_residue = nodes.iterator().next(); selected_residues.add(current_residue); fireUpdatedSelection(); } else if( allow_multiple_selection ) { selected_residues.clear(); current_residue = null; for(Iterator<Residue> i=nodes.iterator(); i.hasNext(); ) selected_residues.add(i.next()); fireUpdatedSelection(); } } public void setSelection(Residue node) { if( active_residues!=null && !active_residues.contains(node)) node = null; if( node==null ) resetSelection(); else { selected_residues.clear(); selected_residues.add(node); current_residue = node; fireUpdatedSelection(); } } public void enforceSelection(Point p) { Residue r = getResidueAtPoint(p); if( r!=null ) enforceSelection(r); } public void enforceSelection(Residue node) { if( active_residues!=null && !active_residues.contains(node)) node = null; if( isSelected(node) ) { current_residue = node; fireUpdatedSelection(); } else setSelection(node); } public boolean enforceSelection() { setSelection(theStructure.getRoot()); return true; } public void addSelection(Collection<Residue> nodes) { if( active_residues!=null ) nodes = new Union<Residue>(active_residues).intersect(nodes); if( nodes!=null && nodes.size()>0 ) { if( allow_multiple_selection ) { for(Residue node : nodes) selected_residues.add(node); current_residue = null; fireUpdatedSelection(); } else if( nodes.size()==1 ) setSelection(nodes.iterator().next()); } } public void addSelection(Residue node) { if( active_residues!=null && !active_residues.contains(node)) node = null; if( node!=null ) { if( allow_multiple_selection ) { selected_residues.add(node); current_residue = node; fireUpdatedSelection(); } else setSelection(node); } } public void addSelectionPathTo(Residue node) { if( active_residues!=null && !active_residues.contains(node)) node = null; if( node!=null ) { if( allow_multiple_selection ) { if( current_residue==null ) selected_residues.add(node); else selected_residues.addAll(Glycan.getPath(current_residue,node)); current_residue = node; fireUpdatedSelection(); } else setSelection(node); } } private Residue findNearest(Point p, Collection<Residue> nodes) { if( p==null ) return null; Residue best_node = null; double best_dist = 0.; for( Residue cur_node : nodes ) { Rectangle cur_rect = theBBoxManager.getCurrent(cur_node); double cur_dist = Geometry.distance(p,cur_rect); if( best_node==null || best_dist>cur_dist ) { best_node = cur_node; best_dist = cur_dist; } } return best_node; } //--------------- // structure navigation public void goToStart() { if( theScrollPane!=null ) { JViewport vp = theScrollPane.getViewport(); vp.setViewPosition(new Point(0,0)); vp.setViewPosition(new Point(0,0)); } } public void goToEnd() { if( theScrollPane!=null ) { JViewport vp = theScrollPane.getViewport(); Dimension all = getPreferredSize(); Dimension view = vp.getExtentSize(); vp.setViewPosition(new Point(0,all.height-view.height)); vp.setViewPosition(new Point(0,all.height-view.height)); } } public void onNavigateUp() { Residue current = getCurrentSelection(); if( current==null ) setSelection(theStructure.getRoot()); else { Residue best_node = theBBoxManager.getNearestUp(current); if( best_node!=null ) setSelection(best_node); } } public void onNavigateDown() { Residue current = getCurrentSelection(); if( current==null ) setSelection(theStructure.getRoot()); else { Residue best_node = theBBoxManager.getNearestDown(current); if( best_node!=null ) setSelection(best_node); } } public void onNavigateLeft() { Residue current = getCurrentSelection(); if( current==null ) setSelection(theStructure.getRoot()); else { Residue best_node = theBBoxManager.getNearestLeft(current); if( best_node!=null ) setSelection(best_node); } } public void onNavigateRight() { Residue current = getCurrentSelection(); if( current==null ) setSelection(theStructure.getRoot()); else { Residue best_node = theBBoxManager.getNearestRight(current); if( best_node!=null ) setSelection(best_node); } } //--------------- // events public void addSelectionChangeListener(SelectionChangeListener l) { if( l!=null ) listeners.add(l); } public void removeSelectionChangeListener(SelectionChangeListener l) { if( l!=null ) listeners.remove(l); } public void fireUpdatedSelection() { for( Iterator<SelectionChangeListener> i=listeners.iterator(); i.hasNext(); ) i.next().selectionChanged(new SelectionChangeEvent(this)); repaint(); showSelection(); } public void showSelection() { if( theScrollPane==null ) return; // update bounding boxes theGlycanRenderer.computeBoundingBoxes(new Union<Glycan>(theStructure),false,true,thePosManager,theBBoxManager); // Rectangle bbox = null; for( Residue r : selected_residues ) bbox = Geometry.union(bbox,theBBoxManager.getCurrent(r)); if( bbox!=null ) { bbox = Geometry.expand(bbox,5); // show bbox in viewport Rectangle view = theScrollPane.getViewport().getViewRect(); int new_x = Geometry.left(view); int new_y = Geometry.top(view); if( Geometry.left(view)>Geometry.left(bbox) ) new_x = Geometry.left(bbox); else if( Geometry.right(view)<Geometry.right(bbox) ) { int min_move = Geometry.right(bbox)-Geometry.right(view); int max_move = Geometry.left(bbox)-Geometry.left(view); new_x += Math.min(min_move,max_move); } if( Geometry.top(view)>Geometry.top(bbox) ) new_y = Geometry.top(bbox); else if( Geometry.bottom(view)<Geometry.bottom(bbox) ) { int min_move = Geometry.bottom(bbox)-Geometry.bottom(view); int max_move = Geometry.top(bbox)-Geometry.top(view); new_y += Math.min(min_move,max_move); } theScrollPane.getViewport().setViewPosition(new Point(new_x,new_y)); theScrollPane.getViewport().setViewPosition(new Point(new_x,new_y)); } } //--------------- // mouse handling public void mouseEntered(MouseEvent e) { } public void mouseExited(MouseEvent e) { } public void mousePressed(MouseEvent e) { if( MouseUtils.isPushTrigger(e) || MouseUtils.isCtrlPushTrigger(e) ) mouse_start_point = e.getPoint(); } public void mouseMoved(MouseEvent e) { } public void mouseDragged(MouseEvent e) { if( mouse_start_point!=null ) { mouse_end_point = e.getPoint(); repaint(); dragAndScroll(e); } repaint(); } public void mouseReleased(MouseEvent e) { if( mouse_end_point!=null ) { Rectangle mouse_rect = Geometry.makeRectangle(mouse_start_point,mouse_end_point); if( MouseUtils.isNothingPressed(e) ) setSelection(theBBoxManager.getNodesInside(mouse_rect)); else if( MouseUtils.isCtrlPressed(e) ) addSelection(theBBoxManager.getNodesInside(mouse_rect)); setCurrentResidue(findNearest(e.getPoint(),selected_residues)); } // reset repaint(); mouse_start_point = null; mouse_end_point = null; repaint(); } public void mouseClicked(MouseEvent e) { Residue r = getResidueAtPoint(e.getPoint()); if( r!=null ) { if( MouseUtils.isSelectTrigger(e) ) setSelection(r); else if( MouseUtils.isAddSelectTrigger(e) ) addSelection(r); else if( MouseUtils.isSelectAllTrigger(e) ) addSelectionPathTo(r); } else if( MouseUtils.isSelectTrigger(e) || MouseUtils.isAddSelectTrigger(e) || MouseUtils.isSelectAllTrigger(e) ) resetSelection(); } private void dragAndScroll(MouseEvent e) { if( theScrollPane==null ) return; // move view if near borders Point point = e.getPoint(); JViewport view = theScrollPane.getViewport(); Rectangle inner = view.getViewRect(); inner.grow(-10,-10); if( !inner.contains(point) ) { Point orig = view.getViewPosition(); if( point.x<inner.x ) orig.x -= 10; else if( point.x>(inner.x+inner.width) ) orig.x += 10; if( point.y<inner.y ) orig.y -= 10; else if( point.y>(inner.y+inner.height) ) orig.y += 10; int maxx = getBounds().width-view.getViewRect().width; int maxy = getBounds().height-view.getViewRect().height; if( orig.x<0 ) orig.x = 0; if( orig.x>maxx ) orig.x = maxx; if( orig.y<0 ) orig.y = 0; if( orig.y>maxy ) orig.y = maxy; view.setViewPosition(orig); } } }