/*
* 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: 1930 $ by $Author: david@nixbioinf.org $ on $Date:: 2010-07-29 #$
*/
/**
@author Alessio Ceroni (a.ceroni@imperial.ac.uk)
*/
package org.eurocarbdb.application.glycoworkbench.plugin;
import org.eurocarbdb.application.glycanbuilder.*;
import org.eurocarbdb.application.glycoworkbench.*;
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.*;
public class FragmentCanvas extends JComponent implements Printable, MouseListener, MouseMotionListener, ActionListener {
public static final int MARGIN_FRAGMENTS = 18;
public static final int MARGIN_COLUMN = 18;
public static final int MARGIN_CHILD = 0;
public static final int MARGIN_CLOSE_BUTTON = 24;
public static final int SIZE_CLOSE_BUTTON = 12;
public static final String TEXT_FONT_FACE = "SansSerif.plain";
public static final int TEXT_SIZE = 12;
protected boolean canvasRequiresRevalidate;
// Classes
public interface SelectionChangeListener {
public void selectionChanged(SelectionChangeEvent e);
}
public static class SelectionChangeEvent {
private FragmentCanvas src;
public SelectionChangeEvent(FragmentCanvas _src) {
src = _src;
}
public FragmentCanvas getSource() {
return src;
}
}
public static class FragmentNode {
public Residue position = null;
public FragmentEntry entry = null;
public Vector<FragmentNode> children = new Vector<FragmentNode>();
public boolean zoom = false;
Rectangle close_bbox = null;
Rectangle fragment_bbox = null;
Rectangle type_bbox = null;
Rectangle mzs_bbox = null;
Rectangle children_bbox = null;
Rectangle node_bbox = null;
Rectangle all_bbox = null;
public FragmentNode(Residue p, Glycan f) {
position = p;
entry = new FragmentEntry(f,Fragmenter.getFragmentType(f));
}
public FragmentNode(Residue p, FragmentEntry _entry) {
position = p;
entry = _entry;
}
public boolean add(FragmentNode toadd) {
if( toadd==null )
return false;
for( FragmentNode child : children ) {
if( child.position==toadd.position &&
child.entry.name.equals(toadd.entry.name) &&
child.entry.structure.equals(toadd.entry.structure) )
return false;
}
children.add(toadd);
return true;
}
public void clearChildren() {
children.clear();
}
}
public class SearchResult {
public static final int NO_BUTTON = 0;
public static final int CLOSE_BUTTON = 1;
protected FragmentNode parent = null;
protected int button = NO_BUTTON;
protected Residue residue = null;
protected Linkage linkage = null;
protected boolean is_on_border = false;
protected boolean can_do_cleavage = false;
protected boolean can_do_ringfragment = false;
public SearchResult() {
}
public SearchResult(FragmentNode fn) {
parent = fn;
}
public SearchResult(FragmentNode fn, int b) {
parent = fn;
button = b;
}
public SearchResult(FragmentNode fn, Residue r) {
parent = fn;
residue = r;
// validate
Glycan s = (parent!=null) ?parent.entry.fragment :null;
if( residue!=null ) {
is_on_border = thePosManager.isOnBorder(residue);
if( is_on_border ) {
if( Fragmenter.canDoCleavage(s,residue,true) )
can_do_cleavage = true;
else
residue = null;
}
else {
if( Fragmenter.canDoRingFragment(s,residue,true) )
can_do_ringfragment = true;
else
residue = null;
}
}
}
public SearchResult(FragmentNode fn, Linkage l) {
parent = fn;
linkage = l;
Glycan s = (parent!=null) ?parent.entry.fragment :null;
if( linkage!=null ) {
if( Fragmenter.canDoCleavage(s,linkage.getChildResidue(),true) )
can_do_cleavage = true;
else
linkage = null;
}
}
public boolean isEmpty() {
return ( parent==null || (button==NO_BUTTON && residue==null && linkage==null) );
}
public FragmentNode getParent() {
return parent;
}
public Linkage getSelectedLinkage() {
return linkage;
}
public HashSet<Linkage> getSelectedLinkages() {
HashSet<Linkage> set = new HashSet<Linkage>();
if( linkage!=null )
set.add(linkage);
return set;
}
public Residue getSelectedResidue() {
return residue;
}
public HashSet<Residue> getSelectedResidues() {
HashSet<Residue> set = new HashSet<Residue>();
if( residue!=null )
set.add(residue);
return set;
}
public Residue getFragmentResidue() {
if( residue!=null )
return residue;
if( linkage!=null )
return linkage.getChildResidue();
return null;
}
public Linkage getFragmentLinkage() {
if( linkage!=null )
return linkage;
if( residue!=null && can_do_cleavage )
return residue.getParentLinkage();
return null;
}
public boolean isCloseButton() {
return (button==CLOSE_BUTTON);
}
public boolean isOnBorder() {
return is_on_border;
}
public boolean canDoCleavage() {
return can_do_cleavage;
}
public boolean canDoRingFragment() {
return can_do_ringfragment;
}
public boolean equals(Object other) {
if( !(other instanceof SearchResult) )
return false;
SearchResult sr = (SearchResult)other;
if( this.parent!=sr.parent )
return false;
if( this.button!=sr.button )
return false;
if( this.residue!=sr.residue )
return false;
if( this.linkage!=sr.linkage )
return false;
return true;
}
public int hashCode() {
return parent.hashCode() + Integer.valueOf(button).hashCode() + residue.hashCode() + linkage.hashCode();
}
}
//
protected static final long serialVersionUID = 0L;
protected JScrollPane theScrollPane = null;
// data
protected Fragmenter fragmenter;
protected FragmentNode theRoot;
// selection
protected FragmentNode with_button_pressed = null;
protected SearchResult current_search = null;
protected FragmentNode current_node;
protected Vector<FragmentNode> all_nodes;
protected HashSet<FragmentNode> selected_nodes;
// painting
protected GlycanRenderer theGlycanRenderer;
protected StyledTextCellRenderer theTextRenderer;
protected Rectangle all_bbox;
protected BBoxManager theBBoxManager;
protected PositionManager thePosManager;
protected boolean is_printing;
protected Cursor cut_cursor = null;
protected ImageIcon minus_button = null;
protected ImageIcon minus_button_pressed = null;
protected ImageIcon cross_button = null;
protected ImageIcon cross_button_pressed = null;
protected JLabel sel_label = new JLabel();
protected Vector<SelectionChangeListener> listeners;
//-----------------------
public FragmentCanvas() {
// init
theGlycanRenderer = new GlycanRenderer();
theTextRenderer = new StyledTextCellRenderer();
thePosManager = new PositionManager();
theBBoxManager = new BBoxManager();
theRoot = null;
current_search = new SearchResult();
current_node = null;
all_nodes = new Vector<FragmentNode>();
selected_nodes = new HashSet<FragmentNode>();
theGlycanRenderer = new GlycanRenderer();
thePosManager = new PositionManager();
theBBoxManager = new BBoxManager();
all_bbox = null;
is_printing = false;
fragmenter = new Fragmenter();
listeners = new Vector<SelectionChangeListener>();
// set the canvas
this.setOpaque(true);
this.setBackground(Color.white);
// load cursor
cut_cursor = FileUtils.createCursor("cut");
minus_button = FileUtils.defaultThemeManager.getImageIcon("fcb_minus");
cross_button = FileUtils.defaultThemeManager.getImageIcon("fcb_cross");
minus_button_pressed = FileUtils.defaultThemeManager.getImageIcon("fcb_minusp2");
cross_button_pressed = FileUtils.defaultThemeManager.getImageIcon("fcb_crossp");
// add mouse events
addMouseMotionListener(this);
addMouseListener(this);
}
public JScrollPane getScrollPane() {
return theScrollPane;
}
public void setScrollPane(JScrollPane sp) {
theScrollPane = sp;
}
public GlycanRenderer getGlycanRenderer() {
return theGlycanRenderer;
}
public void setGlycanRenderer(GlycanRenderer r) {
theGlycanRenderer = r;
}
private void createActions() {
}
private JPopupMenu createPopupMenu(Residue r) {
JPopupMenu menu = new JPopupMenu();
// add actions to create cross ring cleavages for this residue in string order
JMenu ax_menu = new JMenu("A/X fragments");
JMenu a_menu = new JMenu("A fragments");
JMenu x_menu = new JMenu("X fragments");
ax_menu.add( new GlycanAction("cleave=a,x",null,"All positions",-1,"",this) );
a_menu.add( new GlycanAction("cleave=a",null,"All positions",-1,"",this) );
x_menu.add( new GlycanAction("cleave=x",null,"All positions",-1,"",this) );
for( int fp=0; fp<=3; fp++ ) {
for( int lp=fp+2; lp<=5 && (lp-fp)<=4; lp++ ) {
ax_menu.add( new GlycanAction("cleave=a"+fp+""+lp+",x"+fp+""+lp,null,"Position " + fp + "," + lp,-1,"",this) );
a_menu.add( new GlycanAction("cleave=a"+fp+""+lp,null,"Position " + fp + "," + lp,-1,"",this) );
x_menu.add( new GlycanAction("cleave=x"+fp+""+lp,null,"Position " + fp + "," + lp,-1,"",this) );
}
}
menu.add(ax_menu);
menu.add(a_menu);
menu.add(x_menu);
return menu;
}
private JPopupMenu createPopupMenu(Linkage l, boolean on_border) {
JPopupMenu menu = new JPopupMenu();
// add actions to create glycosidic cleavages
if( on_border )
menu.add( new GlycanAction("cleave=y",null,"Y fragment",-1,"",this) );
else {
menu.add( new GlycanAction("cleave=b,y",null,"B/Y fragments",-1,"",this) );
menu.add( new GlycanAction("cleave=c,z",null,"C/Z fragments",-1,"",this) );
menu.add( new GlycanAction("cleave=b",null,"B fragment",-1,"",this) );
menu.add( new GlycanAction("cleave=y",null,"Y fragment",-1,"",this) );
menu.add( new GlycanAction("cleave=c",null,"C fragment",-1,"",this) );
menu.add( new GlycanAction("cleave=z",null,"Z fragment",-1,"",this) );
menu.add( new GlycanAction("cleave=b,y,c,z",null,"All fragments",-1,"",this) );
}
return menu;
}
//-------------------
// JComponent
public Dimension getPreferredSize() {
return theGlycanRenderer.computeSize(all_bbox);
}
public Dimension getMinimumSize() {
return new Dimension(0,0);
}
public PositionManager getPositionManager() {
return thePosManager;
}
//-------------------------
// Data
public void setStructure(Glycan structure) {
if( structure==null )
theRoot = null;
else
theRoot = new FragmentNode(null,structure);
current_search = new SearchResult();
refreshAllNodes();
resetSelection();
this.canvasRequiresRevalidate=true;
repaint();
}
public void setStructure(Glycan structure, Residue frag_at) {
if( structure==null )
theRoot = null;
else {
theRoot = new FragmentNode(null, structure);
if( frag_at!=null ) {
// update position manager
theGlycanRenderer.assignPositions(structure,thePosManager);
// check if it's ring cleavage
if( thePosManager.isOnBorder(frag_at) )
onAddFragments(theRoot,frag_at,"y");
else
onAddFragments(theRoot,frag_at,"a,x");
}
}
current_search = new SearchResult();
refreshAllNodes();
resetSelection();
this.canvasRequiresRevalidate=true;
repaint();
}
public void setStructure(Glycan structure, Linkage frag_at) {
if( structure==null )
theRoot = null;
else {
theRoot = new FragmentNode(null,structure);
if( frag_at!=null )
onAddFragments(theRoot,frag_at.getChildResidue(),"b,y");
}
current_search = new SearchResult();
refreshAllNodes();
resetSelection();
this.canvasRequiresRevalidate=true;
repaint();
}
private boolean addFragment(FragmentNode parent, Residue r, Glycan f) {
if( parent==null || r==null || f==null )
return false;
if( parent.add(new FragmentNode(r,f)) ) {
refreshAllNodes();
return true;
}
return false;
}
private void clearChildren(FragmentNode parent) {
parent.clearChildren();
refreshAllNodes();
}
private void refreshAllNodes() {
all_nodes.clear();
getAllNodes(all_nodes,theRoot);
}
static private void getAllNodes(Vector<FragmentNode> buffer, FragmentNode current) {
if( current==null )
return;
buffer.add(current);
for( FragmentNode child : current.children)
getAllNodes(buffer,child);
}
//-------------------
// 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);
// set scale
GraphicOptions theGraphicOptions = theGlycanRenderer.getGraphicOptions();
boolean old_flag = theGraphicOptions.SHOW_INFO;
double old_scale = theGraphicOptions.SCALE;
theGraphicOptions.SHOW_INFO = false;
// compute bounding boxes
all_bbox = null;
if( theRoot!=null ) {
thePosManager.reset();
theBBoxManager.reset();
// compute columns
Rectangle s_bbox = computeStructuresBoundingBoxes(theGraphicOptions.MARGIN_LEFT,theGraphicOptions.MARGIN_TOP,theRoot);
Rectangle t_bbox = computeTypesBoundingBoxes(Geometry.right(s_bbox) + MARGIN_COLUMN, theRoot);
Rectangle m_bbox = computeMZsBoundingBoxes(Geometry.right(t_bbox) + MARGIN_COLUMN, theRoot);
computeNodeBoundingBoxes(theGraphicOptions.MARGIN_LEFT,theRoot);
// allocate viewport size
if( theScrollPane!=null ) {
int max_width = theScrollPane.getViewport().getExtentSize().width - theGraphicOptions.MARGIN_LEFT - theGraphicOptions.MARGIN_RIGHT - MARGIN_CLOSE_BUTTON;
int cur_width = s_bbox.width + MARGIN_COLUMN + t_bbox.width + MARGIN_COLUMN + m_bbox.width;
if( cur_width < max_width ) {
int delta = max_width - cur_width;
s_bbox = computeStructuresBoundingBoxes(theGraphicOptions.MARGIN_LEFT,theGraphicOptions.MARGIN_TOP,theRoot);
t_bbox = computeTypesBoundingBoxes(Geometry.right(s_bbox) + delta/3 + MARGIN_COLUMN, theRoot);
m_bbox = computeMZsBoundingBoxes(Geometry.right(t_bbox) + delta/3 + MARGIN_COLUMN, theRoot);
computeNodeBoundingBoxes(theGraphicOptions.MARGIN_LEFT,theRoot);
all_bbox = new Rectangle(theGraphicOptions.MARGIN_LEFT, theGraphicOptions.MARGIN_TOP, max_width, 0);
}
}
all_bbox = Geometry.union(all_bbox,s_bbox);
all_bbox = Geometry.union(all_bbox,t_bbox);
all_bbox = Geometry.union(all_bbox,m_bbox);
}
// draw
paint(g2d,theRoot);
paintSelection(g2d);
//System.err.println("Painting fragment canvas");
if(this.canvasRequiresRevalidate==true){
this.canvasRequiresRevalidate=false;
revalidate();
}
// reset scale
theGraphicOptions.setScale(old_scale);
theGraphicOptions.SHOW_INFO = old_flag;
// dispose graphic object
g2d.dispose();
}
private Rectangle computeStructuresBoundingBoxes(int cur_left, int cur_top, FragmentNode fn) {
if( fn==null )
return null;
GraphicOptions theGraphicOptions = theGlycanRenderer.getGraphicOptions();
if( fn.zoom )
theGraphicOptions.setScale(Math.max(theGraphicOptions.SCALE_CANVAS,1.));
else
theGraphicOptions.setScale(0.75*theGraphicOptions.SCALE_CANVAS);
//
fn.all_bbox = new Rectangle(cur_left,cur_top,1,1);
// compute bbox for fragment
fn.fragment_bbox = theGlycanRenderer.computeBoundingBoxes(fn.entry.fragment,cur_left + MARGIN_CLOSE_BUTTON,cur_top,false,true,thePosManager,theBBoxManager);
fn.all_bbox = Geometry.union(fn.all_bbox,fn.fragment_bbox);
// compute bbox for close button
if( fn.children.size()>0 ) {
//if( fn!=theRoot || fn.children.size()>0 ) {
int left = cur_left + MARGIN_CLOSE_BUTTON/3 - SIZE_CLOSE_BUTTON/2;
int top = cur_top + fn.fragment_bbox.height/2 - SIZE_CLOSE_BUTTON/2;
fn.close_bbox = new Rectangle(left,top,SIZE_CLOSE_BUTTON,SIZE_CLOSE_BUTTON);
}
else
fn.close_bbox = null;
cur_left = cur_left + MARGIN_CLOSE_BUTTON;
// compute bbox for children
fn.children_bbox = null;
for( FragmentNode child : fn.children) {
cur_top = Geometry.bottom(fn.all_bbox) + MARGIN_FRAGMENTS;
Rectangle child_bbox = computeStructuresBoundingBoxes(cur_left + MARGIN_CHILD, cur_top, child);
fn.all_bbox = Geometry.union(fn.all_bbox,child_bbox);
fn.children_bbox = Geometry.union(fn.children_bbox,child_bbox);
}
return fn.all_bbox;
}
private Rectangle computeTypesBoundingBoxes(int cur_left, FragmentNode fn) {
Dimension d = getTextBounds(fn.entry.name);
fn.type_bbox = new Rectangle(cur_left,Geometry.midy(fn.fragment_bbox)-d.height/2,d.width,d.height);
Rectangle all_bbox = fn.type_bbox;
for( FragmentNode child : fn.children)
all_bbox = Geometry.union(all_bbox,computeTypesBoundingBoxes(cur_left,child));
return all_bbox;
}
private Rectangle computeMZsBoundingBoxes(int cur_left, FragmentNode fn) {
DecimalFormat df = new DecimalFormat("0.0000");
Dimension d = getTextBounds(df.format(fn.entry.mz_ratio.doubleValue()));
fn.mzs_bbox = new Rectangle(cur_left,Geometry.midy(fn.fragment_bbox)-d.height/2,d.width,d.height);
Rectangle all_bbox = fn.mzs_bbox;
for( FragmentNode child : fn.children)
all_bbox = Geometry.union(all_bbox,computeMZsBoundingBoxes(cur_left,child));
return all_bbox;
}
private void computeNodeBoundingBoxes(int cur_left, FragmentNode fn) {
int width = fn.fragment_bbox.width + MARGIN_COLUMN + fn.type_bbox.width + MARGIN_COLUMN + fn.mzs_bbox.width;
fn.node_bbox = new Rectangle(cur_left,fn.fragment_bbox.y-MARGIN_FRAGMENTS/2,width,fn.fragment_bbox.height+ MARGIN_FRAGMENTS);
for( FragmentNode child : fn.children)
computeNodeBoundingBoxes(cur_left + MARGIN_CHILD, child);
}
private Dimension getTextBounds(String text) {
Component c = theTextRenderer.getRendererComponent(new Font(TEXT_FONT_FACE,Font.PLAIN,TEXT_SIZE),Color.black,this.getBackground(),text);
return c.getPreferredSize();
}
private void paintIcon(Graphics2D g2d, Rectangle bbox, ImageIcon icon) {
if( g2d!=null && bbox!=null && icon!=null ) {
AffineTransform at = new AffineTransform();
at.setToTranslation(bbox.x,bbox.y);
g2d.drawImage(icon.getImage(),at,null);
}
}
private void paintText(Graphics2D g2d, Rectangle bbox, String text) {
//g2d.drawString(fn.entry.name, Geometry.left(fn.type_bbox), Geometry.bottom(fn.type_bbox));
Component c = theTextRenderer.getRendererComponent(new Font(TEXT_FONT_FACE,Font.PLAIN,TEXT_SIZE),Color.black,this.getBackground(),text);
SwingUtilities.paintComponent(g2d,c,this,bbox.x,bbox.y,bbox.width,bbox.height);
}
private void paint(Graphics2D g2d, FragmentNode fn) {
if( fn==null )
return;
GraphicOptions theGraphicOptions = theGlycanRenderer.getGraphicOptions();
if( fn.zoom )
theGraphicOptions.setScale(Math.max(theGraphicOptions.SCALE_CANVAS,1.));
else
theGraphicOptions.setScale(0.75*theGraphicOptions.SCALE_CANVAS);
// create selection
HashSet<Residue> selected_residues = current_search.getSelectedResidues();
HashSet<Linkage> selected_linkages = current_search.getSelectedLinkages();
// paint button
if( !is_printing && fn.close_bbox!=null)
paintIcon(g2d,fn.close_bbox,minus_button);
// paint fragment
theGlycanRenderer.paint(g2d,fn.entry.fragment,selected_residues,selected_linkages,false,true,thePosManager,theBBoxManager);
// paint type
paintText(g2d,fn.type_bbox,fn.entry.name);
// paint mzs
DecimalFormat df = new DecimalFormat("0.0000");
paintText(g2d,fn.mzs_bbox,df.format(fn.entry.mz_ratio.doubleValue()));
// paint separator line
if( Geometry.bottom(fn.fragment_bbox)!=Geometry.bottom(all_bbox) ) {
g2d.setColor(Color.gray);
g2d.drawLine(Geometry.left(fn.fragment_bbox),Geometry.bottom(fn.fragment_bbox) + MARGIN_FRAGMENTS/2,
Geometry.right(all_bbox)+theGraphicOptions.MARGIN_RIGHT + MARGIN_CLOSE_BUTTON,Geometry.bottom(fn.fragment_bbox) + MARGIN_FRAGMENTS/2);
}
// paint children
for( FragmentNode child : fn.children)
paint(g2d,child);
}
private void paintSelection(Graphics2D g2d) {
GraphicOptions theGraphicOptions = theGlycanRenderer.getGraphicOptions();
for(Iterator<FragmentNode> i = all_nodes.iterator(); i.hasNext(); ) {
FragmentNode n = i.next();
if( selected_nodes.contains(n) ) {
Rectangle bbox = n.node_bbox;
for( ; i.hasNext(); ) {
FragmentNode t = i.next();
if( selected_nodes.contains(t) )
bbox = Geometry.union(bbox,t.node_bbox);
else
break;
}
g2d.setColor(UIManager.getColor("Table.selectionBackground"));
g2d.fill(new Rectangle(theGraphicOptions.MARGIN_LEFT/2-3,bbox.y,5,bbox.height));
}
}
// paint cur_structure
if( current_node!=null && current_node.node_bbox!=null ) {
Rectangle cur_bbox = current_node.node_bbox;
UIManager.getBorder("Table.focusCellHighlightBorder").paintBorder(sel_label,g2d,theGraphicOptions.MARGIN_LEFT/2-3,cur_bbox.y,5,cur_bbox.height);
}
}
//------------------
// actions
public void onAddFragments(String arguments) {
onAddFragments(current_search.getParent(),current_search.getFragmentResidue(),arguments);
}
public void onAddFragments(FragmentNode fn, Residue r, String arguments) {
if( fn==null || r==null )
return;
Glycan s = fn.entry.fragment;
Vector<String> fragment_types = TextUtils.tokenize(arguments,",");
for( String t : fragment_types ) {
char type = t.charAt(0);
if( type=='b' ) {
for( Glycan fragment : fragmenter.getAllBFragmentsWithLabiles(s,r) )
addFragment(fn,r,fragment);
}
else if( type=='c' ) {
for( Glycan fragment : fragmenter.getAllCFragmentsWithLabiles(s,r) )
addFragment(fn,r,fragment);
}
else if( type=='y' ) {
if( r.isLabile() )
addFragment(fn,r,fragmenter.getLFragment(s,r));
else {
for( Glycan fragment : fragmenter.getAllYFragmentsWithLabiles(s,r) )
addFragment(fn,r,fragment);
}
}
else if( type=='z' ) {
for( Glycan fragment : fragmenter.getAllZFragmentsWithLabiles(s,r) )
addFragment(fn,r,fragment);
}
else if( type=='a' || type=='x' ) {
if( t.length()==1 ) {
// try all
for( int fp=0; fp<=3; fp++ ) {
for( int lp=fp+2; lp<=5 && (lp-fp)<=4; lp++ ) {
CrossRingFragmentType crt = CrossRingFragmentDictionary.getCrossRingFragmentType(type,fp,lp, r);
if( crt!=null ) {
if( type=='a' ) {
for( Glycan fragment : fragmenter.getAllAFragmentsWithLabiles(s,r,crt) )
addFragment(fn,r,fragment);
}
else {
for( Glycan fragment : fragmenter.getAllXFragmentsWithLabiles(s,r,crt) )
addFragment(fn,r,fragment);
}
}
}
}
}
else {
int fp = (int)(t.charAt(1) - '0');
int lp = (int)(t.charAt(2) - '0');
CrossRingFragmentType crt = CrossRingFragmentDictionary.getCrossRingFragmentType(type,fp,lp, r);
if( crt!=null ) {
if( type=='a' ) {
for( Glycan fragment : fragmenter.getAllAFragmentsWithLabiles(s,r,crt) )
addFragment(fn,r,fragment);
}
else {
for( Glycan fragment : fragmenter.getAllXFragmentsWithLabiles(s,r,crt) )
addFragment(fn,r,fragment);
}
}
}
}
}
this.canvasRequiresRevalidate=true;
repaint();
}
public void print( PrinterJob job ) throws PrinterException {
// do something before
is_printing = true;
job.print();
// do something after
is_printing = false;
}
public int print(Graphics g, PageFormat pageFormat, int pageIndex) throws PrinterException {
if (pageIndex > 0) {
return NO_SUCH_PAGE;
}
else {
Graphics2D g2d = (Graphics2D)g;
g2d.setBackground(Color.white);
g2d.translate(pageFormat.getImageableX(), pageFormat.getImageableY());
Dimension td = this.getPreferredSize();
double sx = pageFormat.getImageableWidth()/td.width;
double sy = pageFormat.getImageableHeight()/td.height;
double s = Math.min(sx,sy);
if( s<1. )
g2d.scale(s,s);
RepaintManager.currentManager(this).setDoubleBufferingEnabled(false);
this.paint(g2d);
RepaintManager.currentManager(this).setDoubleBufferingEnabled(true);
return PAGE_EXISTS;
}
}
//----------------
// selection
public SearchResult getWhatsAt(Point p) {
return getWhatsAt(theRoot,p);
}
private SearchResult getWhatsAt(FragmentNode fn, Point p) {
if( fn==null )
return new SearchResult();
// match button
if( fn.close_bbox!=null && fn.close_bbox.contains(p) )
return new SearchResult(fn,SearchResult.CLOSE_BUTTON);
// search in the fragment
SearchResult ret = getWhatsAt(fn,fn.entry.fragment,p);
if( !ret.isEmpty() )
return ret;
// search in the children
for( FragmentNode child : fn.children ) {
ret = getWhatsAt(child,p);
if( !ret.isEmpty() )
return ret;
}
return new SearchResult(fn);
}
private SearchResult getWhatsAt(FragmentNode fn, Glycan f, Point p) {
if( f==null )
return new SearchResult(fn);
SearchResult ret = getWhatsAt(fn,f.getRoot(),p);
if( !ret.isEmpty() )
return ret;
return getWhatsAt(fn,f.getBracket(),p);
}
private SearchResult getWhatsAt(FragmentNode fn, Residue r, Point p) {
if( r==null )
return new SearchResult(fn);
// match current
Rectangle cur_bbox = theBBoxManager.getCurrent(r);
if( cur_bbox==null )
return new SearchResult(fn);
if( cur_bbox.contains(p) )
return new SearchResult(fn,r);
for( Linkage l : r.getChildrenLinkages() ) {
// match child
Residue child = l.getChildResidue();
Rectangle child_bbox = theBBoxManager.getCurrent(child);
if( child_bbox!=null ) {
if( child_bbox.contains(p) )
return new SearchResult(fn,child);
// match link
if( Geometry.distance(p,Geometry.center(cur_bbox),Geometry.center(child_bbox))<4. )
return new SearchResult(fn,l);
// search in the child
SearchResult ret = getWhatsAt(fn,child,p);
if( !ret.isEmpty() )
return ret;
}
}
return new SearchResult(fn);
}
private void updateSearch(MouseEvent e) {
// search
SearchResult ret = getWhatsAt(e.getPoint());
// decide actions
boolean to_repaint = (!current_search.equals(ret));
boolean on_something = ret.canDoCleavage() || ret.canDoRingFragment();
// set cursor
if( on_something && getCursor()!=cut_cursor )
setCursor(cut_cursor);
else if( !on_something && getCursor()==cut_cursor )
setCursor(Cursor.getDefaultCursor());
// repaint canvas
if( to_repaint ){
this.canvasRequiresRevalidate=true;
repaint();
}
current_search = ret;
}
public FragmentNode getNodeAtPoint(Point p) {
for( FragmentNode n : all_nodes) {
if( n.node_bbox!=null && n.node_bbox.contains(p) )
return n;
}
return null;
}
public boolean hasSelection() {
return (selected_nodes.size()>0);
}
public void resetSelection() {
selected_nodes.clear();
current_node = null;
fireUpdatedSelection();
}
public boolean hasCurrentNode() {
return (current_node!=null);
}
public FragmentNode getCurrentNode() {
return current_node;
}
private void setCurrentNode(FragmentNode node) {
if( node!=null )
selected_nodes.add(node);
current_node = node;
fireUpdatedSelection();
}
public boolean isSelected(FragmentNode node) {
if( node==null )
return false;
return selected_nodes.contains(node);
}
public Collection<FragmentNode> getSelectedNodes() {
return selected_nodes;
}
public Collection<Glycan> getSelectedFragments() {
Vector<Glycan> ret = new Vector<Glycan>();
for( FragmentNode fn : selected_nodes ) {
if( fn.entry.fragment!=null )
ret.add(fn.entry.fragment);
}
return ret;
}
public void setSelection(FragmentNode node) {
if( node==null )
resetSelection();
else {
selected_nodes.clear();
selected_nodes.add(node);
current_node = node;
fireUpdatedSelection();
}
}
public void addSelection(FragmentNode node) {
if( node!=null ) {
selected_nodes.add(node);
current_node = node;
fireUpdatedSelection();
}
}
public void addSelectionPathTo(FragmentNode node) {
if( node!=null ) {
if( current_node==null )
selected_nodes.add(node);
else
selected_nodes.addAll(getPath(current_node,node));
current_node = node;
fireUpdatedSelection();
}
}
private Vector<FragmentNode> getPath(FragmentNode from, FragmentNode to) {
Vector<FragmentNode> ret = new Vector<FragmentNode>();
if( from==null || to==null )
return ret;
int ind1 = all_nodes.indexOf(from);
int ind2 = all_nodes.indexOf(to);
if( ind1<=ind2 ) {
for( int i=ind1; i<=ind2; i++ )
ret.add(all_nodes.elementAt(i));
}
else {
for( int i=ind2; i<=ind1; i++ )
ret.add(all_nodes.elementAt(i));
}
return ret;
}
public void toggleZoom(FragmentNode node) {
if( node!=null ) {
node.zoom = !node.zoom;
this.canvasRequiresRevalidate=true;
repaint();
}
}
//----------------------------
// listeners
public void actionPerformed(ActionEvent e) {
String action = GlycanAction.getAction(e);
String param = GlycanAction.getParam(e);
if( action.equals("cleave") )
onAddFragments(param);
}
private void showPopup(MouseEvent e) {
if( current_search.canDoCleavage() )
createPopupMenu(current_search.getFragmentLinkage(),current_search.isOnBorder()).show(this, e.getX(), e.getY());
else if( current_search.canDoRingFragment() )
createPopupMenu(current_search.getFragmentResidue()).show(this, e.getX(), e.getY());
}
public void drawButton(FragmentNode fn, ImageIcon icon) {
Graphics2D g2d = (Graphics2D)this.getGraphics();
paintIcon(g2d,with_button_pressed.close_bbox,icon);
g2d.dispose();
}
public void mousePressed(MouseEvent e) {
if( MouseUtils.isPopupTrigger(e) ) {
updateSearch(e);
showPopup(e);
}
else if( MouseUtils.isPushTrigger(e) ) {
updateSearch(e);
if( current_search.isCloseButton() ) {
// paint pressed button
with_button_pressed = current_search.getParent();
drawButton(with_button_pressed,minus_button_pressed);
}
}
}
public void mouseDragged(MouseEvent e) {
if( with_button_pressed!=null ) {
updateSearch(e);
if( current_search.getParent()!=with_button_pressed || !current_search.isCloseButton() ) {
// reset button
drawButton(with_button_pressed,minus_button);
with_button_pressed = null;
}
}
}
public void mouseReleased(MouseEvent e) {
if( with_button_pressed!=null ) {
updateSearch(e);
if( current_search.getParent()==with_button_pressed && current_search.isCloseButton() ) {
// clear children
clearChildren(current_search.parent);
}
with_button_pressed = null;
this.canvasRequiresRevalidate=true;
repaint();
}
if( MouseUtils.isPopupTrigger(e) ) {
updateSearch(e);
showPopup(e);
}
}
public void mouseClicked(MouseEvent e) {
// update search
if( MouseUtils.isSelectTrigger(e) ) {
updateSearch(e);
if( current_search.canDoCleavage() ) {
// glycosidic cleavages
onAddFragments("b,y");
}
if( current_search.canDoRingFragment() ) {
// cross ring fragments
onAddFragments("a,x");
}
}
// update selection
FragmentNode n = getNodeAtPoint(e.getPoint());
if( n!=null ) {
if( MouseUtils.isSelectTrigger(e) )
setSelection(n);
else if( MouseUtils.isAddSelectTrigger(e) )
addSelection(n);
else if( MouseUtils.isSelectAllTrigger(e) )
addSelectionPathTo(n);
else if( MouseUtils.isActionTrigger(e) )
toggleZoom(n);
}
else
resetSelection();
}
public void mouseEntered(MouseEvent e) {
}
public void mouseExited(MouseEvent e) {
}
public void mouseMoved(MouseEvent e) {
updateSearch(e);
}
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));
this.canvasRequiresRevalidate=true;
repaint();
//showSelection();
}
}