Subversion Repositories wimsdev

Rev

Rev 3662 | Blame | Compare with Previous | Last modification | View Log | RSS feed

  1. /*
  2.     Sketch Elements: Chemistry molecular diagram drawing tool.
  3.    
  4.     (c) 2005 Dr. Alex M. Clark
  5.    
  6.     Released as GNUware, under the Gnu Public License (GPL)
  7.    
  8.     See www.gnu.org for details.
  9. */
  10.  
  11. package WIMSchem;
  12.  
  13. import java.io.*;
  14. import java.util.*;
  15. import java.text.*;
  16. import java.awt.*;
  17. import java.awt.geom.*;
  18. import java.awt.event.*;
  19. import java.awt.datatransfer.*;
  20. import java.awt.dnd.*;
  21. import javax.swing.*;
  22. import javax.swing.event.*;
  23.  
  24. /*
  25.     Custom widget for viewing and editing a molecular structure. The widget is a rather heavy one, and features a lot of the work-horse
  26.     functions for various tools, including the specific details of user interaction, and drawing of the structure itself, with various
  27.     annotations for editing and selection. Can optionally be used as a lightweight widget with just view and selection.
  28. */
  29.  
  30. public class EditorPane extends JComponent
  31.     implements MouseListener, MouseMotionListener, FocusListener, KeyListener, MouseWheelListener, ComponentListener
  32. {
  33.     private Molecule mol;
  34.     private boolean editable=true,hasBorder=false,autoScale=false;
  35.     private static final double IDEALBOND=1.5; // stylised bond distance (Angstroms)
  36.     // note: px=(atomX+offsetX)*scale; ax=px/scale-offsetX; offsetX=px/scale-ax (and same for Y)
  37.     private double offsetX=0,offsetY=0; // in Angstrom units
  38.     private double scale=DrawMolecule.DEFSCALE; // pixels per Angstrom
  39.     public boolean[] selected=null,dragged=null;
  40.     private double[] px=null,py=null,rw=null,rh=null;
  41.     // !! private double[] bfx=null,bfy=null,btx=null,bty=null;
  42.     private int highlightAtom=0,highlightBond=0;
  43.     boolean showHydr=false;
  44.     /*** jm.evers 27/12/2008 **************/
  45.     public static boolean[] bondselection = null;
  46.     public static boolean[] atomselection = null;
  47.     /***************************************/  
  48.    
  49.     private int showMode=ArrangeMolecule.SHOW_ELEMENTS;
  50.     private boolean showSter = false;
  51.  
  52.     private static final int TOOL_CURSOR=1;
  53.     private static final int TOOL_ROTATOR=2;
  54.     private static final int TOOL_ERASOR=3;
  55.     private static final int TOOL_ATOM=4;
  56.     private static final int TOOL_BOND=5;
  57.     private static final int TOOL_CHARGE=6;
  58.     private static final int TOOL_TEMPLATE=7;
  59.     /***** jm.evers 4/3/2010  ************/
  60.     private static final int TOOL_SELECT=8;
  61.     /************************ ************/
  62.    
  63.     private static final int DRAG_SELECT=1;
  64.     private static final int DRAG_MOVE=2;
  65.     private static final int DRAG_COPY=3;
  66.     private static final int DRAG_SCALE=4;
  67.     private static final int DRAG_ROTATE=5;
  68.    
  69.     private int trackX=-1,trackY=-1; // last seen position of mouse
  70.    
  71.     private boolean isSelectionPane=false; // false=is for editing; true=is for viewing and selecting only
  72.     private int selBoxW=0,selBoxH=0; // size to this, for selection panes
  73.    
  74.     private MolSelectListener selectListen=null;
  75.        
  76.     private int tool=0;
  77.     private int toolDragReason=0;
  78.     private double toolDragX1,toolDragY1,toolDragX2,toolDragY2;
  79.     private String toolAtomType=null;
  80.     private boolean toolAtomDrag,toolAtomSnap;
  81.     private int toolAtomEditSel=0,toolAtomEditX,toolAtomEditY;
  82.     private JTextField toolAtomEditBox=null;
  83.  
  84.     private int toolBondOrder=0,toolBondType=0,toolBondFrom=0,toolBondHit=0;
  85.     private double toolBondFromX=0,toolBondFromY=0,toolBondToX=0,toolBondToY=0;
  86.     private boolean toolSnap,toolBondDrag=false;
  87.    
  88.     private int toolCharge=0;
  89.    
  90.     private static final int UNDO_LEVELS=10;
  91.     private class EditState
  92.     {
  93.         Molecule Molecule;
  94.         boolean Selected[];
  95.     };
  96.     private EditState[] undo=null,redo=null;
  97.    
  98.     private Molecule template=null,templDraw=null;
  99.     private int templateIdx=0;
  100.    
  101.     private Molecule lastCleanMol=null;
  102.     private boolean lastDirty=false;
  103.  
  104.     // ------------------ public functions --------------------
  105.  
  106.     // Constructor for fully-fledged interactive editing panes.
  107.     public EditorPane() {init();}
  108.  
  109.     // Constructor for "selection only" editor panes.
  110.     public EditorPane(int width,int height)
  111.     {
  112.         isSelectionPane=true;
  113.         selBoxW=width;
  114.         selBoxH=height;
  115.         init();
  116.     }
  117.    
  118.     private void init()
  119.     {
  120.         mol=new Molecule();
  121.         /**** jm.evers ********/
  122.         if(MainApplet.USER_SELECTION){
  123.             atomselection=new boolean[mol.numAtoms()+32];
  124.             bondselection=new boolean[mol.numBonds()+32];
  125.             for(int p=0; p<mol.numAtoms()+32 ; p++){
  126.                 atomselection[p] = false;
  127.                 bondselection[p] = false;
  128.             }
  129.         }
  130.         /**********************/
  131.  
  132.         determineSize();
  133.         addMouseListener(this);
  134.         addMouseMotionListener(this);
  135.         addMouseWheelListener(this);
  136.         addComponentListener(this);
  137.     }
  138.    
  139.     // call this function to cause the editor to become a receptacle for dragged molecules
  140.     public void enableDnD()
  141.     {
  142.         setTransferHandler(new TransferMoleculeDest(this));
  143.     }
  144.    
  145.     // obtain underlying molecule; not a copy, careful about modifying
  146.     public Molecule molData() {return mol;}
  147.    
  148.     public boolean isEmpty() {return mol.numAtoms()==0;}
  149.    
  150.     // unit operation equivalent to deleting all atoms
  151.     public void clear()
  152.     {
  153.         cacheUndo();
  154.         if(MainApplet.USER_SELECTION){
  155.             atomselection = new boolean[256];
  156.             bondselection = new boolean[256];
  157.         }
  158.         mol=new Molecule();
  159.         clearTemporary();
  160.         determineSize();
  161.         repaint();
  162.        
  163.         checkDirtiness();
  164.     }
  165.  
  166.     // override the underlying molecule
  167.     public void replace(Molecule Mol) {replace(Mol,false,true);}
  168.     public void replace(Molecule Mol,boolean ClearSelection) {replace(Mol,ClearSelection,true);}
  169.     public void replace(Molecule Mol,boolean ClearSelection,boolean Repaint)
  170.     {
  171.         if (mol.numAtoms()!=Mol.numAtoms()) ClearSelection=true;
  172.         mol=Mol;
  173.         clearTemporary(ClearSelection);
  174.         if (Repaint) repaint();
  175.     }
  176.    
  177.     // set which object, if any, gets a response when an atom is "selected" with a mouse click
  178.     public void setMolSelectListener(MolSelectListener listen) {selectListen=listen;}
  179.    
  180.     // by default the editor pane captures lots of events and allows much editor; this function can be used to turn it off
  181.     public void setEditable(boolean Editable) {editable=Editable;}
  182.    
  183.     // if true, will draw a border around the edge
  184.     public void setBorder(boolean HasBorder) {hasBorder=HasBorder;}
  185.  
  186.     // if true, every time the size changes, the molecule will scale-to-fit    
  187.     public void setAutoScale(boolean AutoScale) {autoScale=AutoScale;}
  188.  
  189.     // informs the editor that the current state has been synchronised with what is in a disk file, or something equivalent
  190.     public void notifySaved()
  191.     {
  192.         lastCleanMol=mol.clone();
  193.         lastDirty=false;
  194.         if (selectListen!=null) selectListen.dirtyChanged(false);
  195.     }
  196.    
  197.     // dirty==true when there have been some changes since the last modification
  198.     public boolean isDirty() {return lastDirty;}
  199.    
  200.     // checks to see whether the current molecule is the same as the last saved state; notifies if different; note that this is done by
  201.     // an actual molecule comparison, which makes tracking changes a lot simpler, and also a {do something/restore it somehow} sequence
  202.     // is functionally equivalent to undo, which is nice
  203.     private void checkDirtiness()
  204.     {
  205.         boolean nowDirty=mol.compareTo(lastCleanMol)!=0;
  206.        
  207.         if (nowDirty!=lastDirty)
  208.         {
  209.             if (selectListen!=null) selectListen.dirtyChanged(nowDirty);
  210.             lastDirty=nowDirty;
  211.         }
  212.     }
  213.        
  214.     // affect the way the molecule is rendered
  215.     public void setShowMode(int ShowMode)
  216.     {
  217.         if (showMode==ShowMode) return;
  218.         showMode=ShowMode;
  219.         repaint();
  220.     }
  221.     public void setShowHydrogens(boolean ShowHydr)
  222.     {
  223.         if (showHydr==ShowHydr) return;
  224.         showHydr=ShowHydr;
  225.         repaint();
  226.     }
  227.     public void setShowStereolabels(boolean ShowSter)
  228.     {
  229.         if (showSter==ShowSter) return;
  230.         showSter=ShowSter;
  231.         repaint();
  232.     }
  233.    
  234.     // notify selection of various tools
  235.     public void setToolCursor()
  236.     {
  237.         tool=TOOL_CURSOR;
  238.         repaint();
  239.     }
  240.     public void setToolRotator()
  241.     {
  242.         tool=TOOL_ROTATOR;
  243.         repaint();
  244.     }
  245.     public void setToolErasor()
  246.     {
  247.         tool=TOOL_ERASOR;
  248.         repaint();
  249.     }
  250.     public void setToolAtom(String Atom)
  251.     {
  252.         tool=TOOL_ATOM;
  253.         toolAtomType=Atom;
  254.         toolAtomDrag=false;
  255.         toolAtomSnap=false;
  256.         toolBondFrom=0;
  257.         toolBondToX=0;
  258.         toolBondToY=0;
  259.         repaint();
  260.     }
  261.     public void setToolBond(int Order)
  262.     {
  263.         tool=TOOL_BOND;
  264.         toolBondFrom=0;
  265.         if (Order>=0) {toolBondOrder=Order; toolBondType=Molecule.BONDTYPE_NORMAL;}
  266.         else
  267.         {
  268.             toolBondOrder=1;
  269.             if (Order==-1) toolBondType=Molecule.BONDTYPE_INCLINED;
  270.             else if (Order==-2) toolBondType=Molecule.BONDTYPE_DECLINED;
  271.             else if (Order==-3) toolBondType=Molecule.BONDTYPE_UNKNOWN;
  272.         }
  273.         repaint();
  274.     }
  275.     public void setToolCharge(int DChg)
  276.     {
  277.         tool=TOOL_CHARGE;
  278.         toolCharge=DChg;
  279.     }
  280.     public void setToolTemplate(Molecule Templ,int Idx)
  281.     {
  282.         tool=TOOL_TEMPLATE;
  283.         template=Templ;
  284.         templateIdx=Idx;
  285.         repaint();
  286.     }
  287.    
  288.     // whether or not there is anything in the undo/redo stacks
  289.     public boolean canUndo() {return undo!=null && undo[0]!=null;}
  290.     public boolean canRedo() {return redo!=null && redo[0]!=null;}
  291.    
  292.     // cause the actual undo/redo to happen
  293.     public void undo()
  294.     {
  295.         if (!canUndo()) return;
  296.        
  297.         if (redo==null) redo=new EditState[UNDO_LEVELS];
  298.         for (int n=UNDO_LEVELS-1;n>0;n--) redo[n]=redo[n-1];
  299.         redo[0]=new EditState();
  300.         redo[0].Molecule=mol;
  301.         redo[0].Selected=selected;
  302.        
  303.         mol=undo[0].Molecule;
  304.         selected=undo[0].Selected;
  305.         for (int n=0;n<UNDO_LEVELS-1;n++) undo[n]=undo[n+1];
  306.         clearTemporary(false);
  307.         determineSize();
  308.         repaint();
  309.        
  310.         checkDirtiness();
  311.     }
  312.     public void redo()
  313.     {
  314.         if (!canRedo()) return;
  315.        
  316.         if (undo==null) undo=new EditState[UNDO_LEVELS];
  317.         for (int n=UNDO_LEVELS-1;n>0;n--) undo[n]=undo[n-1];
  318.         undo[0]=new EditState();
  319.         undo[0].Molecule=mol;
  320.         undo[0].Selected=selected;
  321.        
  322.         mol=redo[0].Molecule;
  323.         selected=redo[0].Selected;
  324.         for (int n=0;n<UNDO_LEVELS-1;n++) redo[n]=redo[n+1];
  325.        
  326.         clearTemporary(false);
  327.         determineSize();
  328.         repaint();
  329.        
  330.         checkDirtiness();
  331.     }
  332.    
  333.     // fits the molecule on the screen and centres everything; very pleasant thing to have at certain junctures, but not too often
  334.     public void scaleToFit() {scaleToFit(20);}
  335.     public void scaleToFit(double MaxScale)
  336.     {
  337.         clearTemporary();
  338.  
  339.         double mw=2+mol.rangeX(),mh=2+mol.rangeY();
  340.         Rectangle vis=getVisibleRect();
  341.         if (vis.getWidth()==0 || vis.getHeight()==0) vis=new Rectangle(0,0,getWidth(),getHeight());
  342.         double sw=selBoxW>vis.getWidth() ? selBoxW : vis.getWidth();
  343.         double sh=selBoxH>vis.getHeight() ? selBoxH : vis.getHeight();
  344.         scale=Math.min(Math.min(sw/mw,sh/mh),MaxScale);
  345.         offsetX=0.5*(sw/scale-mol.rangeX())-mol.minX();
  346.         offsetY=0.5*(sh/scale-mol.rangeY())+mol.maxY();
  347.     }
  348.  
  349.     // change the magnification, and adjust scrollbars etc accordingly
  350.     public void zoomFull()
  351.     {
  352.         scaleToFit();
  353.         determineSize();
  354.         repaint();
  355.     }
  356.     public void zoomIn(double Mag)
  357.     {
  358.         scale*=Mag;
  359.         determineSize();
  360.         repaint();
  361.     }
  362.     public void zoomOut(double Mag)
  363.     {
  364.         scale/=Mag;
  365.         determineSize();
  366.         repaint();
  367.     }
  368.     /****** jm.evers ****************/
  369.      public void deSelectAll(){
  370.        if(MainPanel.appletMode){
  371.         int i = atomselection.length;
  372.         for(int p = 0 ; p < i ; p++ ){
  373.          atomselection[p] = false;
  374.         }
  375.         i = bondselection.length;
  376.         for(int p = 0 ; p < i ; p++ ){
  377.          bondselection[p] = false;
  378.         }
  379.         System.out.println("EditorPane deSelectAll()");
  380.         repaint();
  381.        }    
  382.     }
  383.     public void Select(){
  384.         System.out.println("EditorPane Select()");
  385.         tool = TOOL_SELECT;
  386.     }
  387.     /*********************************/
  388.                              
  389.    
  390.     // select all atoms
  391.     public void selectAll()
  392.     {
  393.         selected=new boolean[mol.numAtoms()];
  394.         for (int n=0;n<mol.numAtoms();n++) selected[n]=true;
  395.         repaint();
  396.     }
  397.    
  398.     // finds a nice place to put the new fragment which does not overlap existing content, then appends the atoms & bonds; note that
  399.     // ownership of Frag is assumed
  400.     public void addArbitraryFragment(Molecule Frag)
  401.     {
  402.         if (Frag.numAtoms()==0) return;
  403.    
  404.         cacheUndo();
  405.         if (mol.numAtoms()==0)
  406.         {
  407.             mol=Frag;
  408.             clearTemporary();
  409.             scaleToFit();
  410.             determineSize();
  411.             repaint();
  412.             checkDirtiness();
  413.             return;
  414.         }
  415.    
  416.         final double dirX[]={1,0,-1,1,-1,1,0,-1},dirY[]={1,1,1,0,0,-1,-1,-1};
  417.         double dx[]=new double[8],dy[]=new double[8],score[]=new double[8];
  418.        
  419.         for (int n=0;n<8;n++)
  420.         {
  421.             double vx=dirX[n],vy=dirY[n];
  422.  
  423.             if (n==0 || n==3 || n==5) {dx[n]=mol.minX()-Frag.maxX();}
  424.             else if (n==2 || n==4 || n==7) {dx[n]=mol.maxX()-Frag.minX();}
  425.             else dx[n]=0.5*(mol.minX()+mol.maxX()-Frag.minX()-Frag.maxX());
  426.            
  427.             if (n==5 || n==6 || n==7) {dy[n]=mol.minY()-Frag.maxY();}
  428.             else if (n==0 || n==1 || n==2) {dy[n]=mol.maxY()-Frag.minY();}
  429.             else dy[n]=0.5*(mol.minY()+mol.maxY()-Frag.minY()-Frag.maxY());
  430.            
  431.             dx[n]-=vx;
  432.             dy[n]-=vy;
  433.             score[n]=fragPosScore(Frag,dx[n],dy[n]);
  434.            
  435.             vx*=0.25;
  436.             vy*=0.25;
  437.             for (int iter=100;iter>0;iter--)
  438.             {
  439.                 double iscore=fragPosScore(Frag,dx[n]+vx,dy[n]+vy);
  440.                 if (iscore<=score[n]) break;
  441.                 score[n]=iscore;
  442.                 dx[n]+=vx;
  443.                 dy[n]+=vy;
  444.             }
  445.             for (int iter=100;iter>0;iter--) for (int d=0;d<8;d++)
  446.             {
  447.                 vx=dirX[d]*0.1;
  448.                 vy=dirY[d]*0.1;
  449.                 double iscore=fragPosScore(Frag,dx[n]+vx,dy[n]+vy);
  450.                 if (iscore<=score[n]) break;
  451.                 score[n]=iscore;
  452.                 dx[n]+=vx;
  453.                 dy[n]+=vy;
  454.             }
  455.         }
  456.        
  457.         int best=0;
  458.         for (int n=1;n<8;n++) if (score[n]>score[best]) best=n;
  459.        
  460.         for (int n=1;n<=Frag.numAtoms();n++) Frag.setAtomPos(n,Frag.atomX(n)+dx[best],Frag.atomY(n)+dy[best]);
  461.         int base=mol.numAtoms();
  462.         mol.append(Frag);
  463.        
  464.         clearTemporary();
  465.         scaleToFit();
  466.         determineSize();
  467.        
  468.         selected=new boolean[mol.numAtoms()];
  469.         for (int n=0;n<mol.numAtoms();n++) selected[n]=n>=base;
  470.        
  471.         repaint();
  472.         checkDirtiness();
  473.     }
  474.  
  475.     // like above, except that the destination centre of gravity for positioning the new fragment is provided, in
  476.     // component coordinates; note that ownership of Frag is claimed
  477.     private void addFragmentPosition(Molecule Frag,int X,int Y)
  478.     {
  479.         if (Frag.numAtoms()==0) return;
  480.        
  481.         double cx=xToAng(X),cy=yToAng(Y);
  482.         double loX=Frag.atomX(1),hiX=loX,loY=Frag.atomY(1),hiY=loY;
  483.         for (int n=2;n<=Frag.numAtoms();n++)
  484.         {
  485.             loX=Math.min(loX,Frag.atomX(n));
  486.             hiX=Math.max(hiX,Frag.atomX(n));
  487.             loY=Math.min(loY,Frag.atomY(n));
  488.             hiY=Math.max(hiY,Frag.atomY(n));
  489.         }
  490.         cx-=0.5*(loX+hiX);
  491.         cy-=0.5*(loY+hiY);
  492.         for (int n=1;n<=Frag.numAtoms();n++) Frag.setAtomPos(n,Frag.atomX(n)+cx,Frag.atomY(n)+cy);
  493.        
  494.         cacheUndo();
  495.         int base=mol.numAtoms();
  496.         mol.append(Frag);
  497.        
  498.         clearTemporary();
  499.         scaleToFit();
  500.         determineSize();
  501.        
  502.         selected=new boolean[mol.numAtoms()];
  503.         for (int n=0;n<mol.numAtoms();n++) selected[n]=n>=base;
  504.        
  505.         repaint();
  506.         checkDirtiness();
  507.     }
  508.    
  509.     // scoring function for above: more congested is better, but any two atoms < 1A = zero; post-biased to favour square aspect ratio
  510.     private double fragPosScore(Molecule Frag,double DX,double DY)
  511.     {
  512.         double score=0;
  513.         for (int i=1;i<=mol.numAtoms();i++)
  514.         for (int j=1;j<=Frag.numAtoms();j++)
  515.         {
  516.             double dx=Frag.atomX(j)+DX-mol.atomX(i),dy=Frag.atomY(j)+DY-mol.atomY(i);
  517.             double dist2=dx*dx+dy*dy;
  518.             if (dist2<1) return 0;
  519.             score+=1/dist2;
  520.         }
  521.         double minX=Math.min(Frag.minX()+DX,mol.minX()),maxX=Math.max(Frag.maxX()+DX,mol.maxX());
  522.         double minY=Math.min(Frag.minY()+DY,mol.minY()),maxY=Math.max(Frag.maxY()+DY,mol.maxY());
  523.         double rangeX=Math.max(1,maxX-minX),rangeY=Math.max(1,maxY-minY);
  524.         double ratio=Math.max(rangeX/rangeY,rangeY/rangeX);
  525.         return score/ratio;
  526.     }
  527.    
  528.     // returns the # of atoms in selection set
  529.     public int countSelected()
  530.     {
  531.         if (selected==null) return 0;
  532.         int num=0;
  533.         for (int n=0;n<mol.numAtoms();n++) if (selected[n]) num++;
  534.         return num;
  535.     }
  536.    
  537.     // returns array of atoms which are presently selected, or everything if none
  538.     public ArrayList<Integer> selectedIndices()
  539.     {
  540.         ArrayList<Integer> selidx=new ArrayList<Integer>();
  541.         if (selected!=null) for (int n=0;n<mol.numAtoms();n++) if (selected[n]) selidx.add(n+1);
  542.         if (selidx.size()==0) for (int n=1;n<=mol.numAtoms();n++) selidx.add(n);
  543.         return selidx;
  544.     }
  545.    
  546.     // returns a subgraph of the molecule corresponding to selected atoms - or if none, the whole thing
  547.     public Molecule selectedSubgraph()
  548.     {
  549.         if (selected==null) return mol.clone();
  550.         int sum=0;
  551.         for (int n=0;n<mol.numAtoms();n++) if (selected[n]) sum++;
  552.         if (sum==0) return mol.clone();
  553.  
  554.         return mol.subgraph(selected);
  555.     }
  556.    
  557.     // deletes selected atoms, or all atoms if none selected
  558.     public void deleteSelected()
  559.     {
  560.         cacheUndo();
  561.  
  562.         boolean anySelected=false;
  563.         if (selected!=null) for (int n=0;n<mol.numAtoms();n++) if (selected[n]) {anySelected=true; break;}
  564.         if (!anySelected) return;
  565.  
  566.         for (int n=mol.numAtoms()-1;n>=0;n--) if (selected[n]) mol.deleteAtomAndBonds(n+1);
  567.  
  568.         clearTemporary();
  569.         determineSize();
  570.         repaint();
  571.         checkDirtiness();
  572.     }
  573.    
  574.     // switch between explicit/implicit modes; going to explicit marks current calculated value as absolute
  575.     public void hydrogenSetExplicit(boolean IsExpl) {hydrogenSetExplicit(IsExpl,Molecule.HEXPLICIT_UNKNOWN);}
  576.     public void hydrogenSetExplicit(boolean IsExpl,int NumExpl)
  577.     {
  578.         cacheUndo();
  579.  
  580.         ArrayList<Integer> sel=selectedIndices();
  581.         for (int n=0;n<sel.size();n++)
  582.         {
  583.             int i=sel.get(n).intValue();
  584.             if (IsExpl) mol.setAtomHExplicit(i,mol.atomHydrogens(i)); else mol.setAtomHExplicit(i,NumExpl);
  585.         }
  586.         repaint();
  587.  
  588.         checkDirtiness();
  589.     }
  590.    
  591.     // any hydrogens which are implicit or explicit are actually created as nodes in the molecular graph; the explicit value of each
  592.     // parent is set to unknown
  593.     public void hydrogenCreateActual()
  594.     {
  595.         cacheUndo();
  596.  
  597.         ArrayList<Integer> sel=selectedIndices();
  598.         int score[]=new int[360];
  599.         for (int n=0;n<sel.size();n++)
  600.         {
  601.             int i=sel.get(n).intValue();
  602.             int hy=mol.atomHydrogens(i);
  603.             if (hy==0) continue;
  604.            
  605.             for (int j=0;j<360;j++) score[j]=0;
  606.             int adj[]=mol.atomAdjList(i);
  607.             for (int j=0;j<adj.length;j++)
  608.             {
  609.                 int iang=(int)(Math.atan2(mol.atomY(adj[j])-mol.atomY(i),mol.atomX(adj[j])-mol.atomX(i))*180/Math.PI);
  610.                 if (iang<0) iang+=360;
  611.                 score[iang]=-1; score[(iang+1)%360]=-1; score[(iang+359)%360]=-1;
  612.                 int i0=(iang+180)%360,i1=(iang+120)%360,i2=(iang+240)%360;
  613.                 if (score[i0]>=0) score[i0]+=2;
  614.                 if (score[i1]>=0) score[i1]+=4;
  615.                 if (score[i2]>=0) score[i2]+=4;
  616.             }
  617.            
  618.             while (hy>0)
  619.             {
  620.                 int iang=0;
  621.                 for (int j=1;j<360;j++) if (score[j]>score[iang]) iang=j;
  622.                 int num=mol.addAtom("H",mol.atomX(i)+Math.cos(iang*Math.PI/180.0),mol.atomY(i)+Math.sin(iang*Math.PI/180.0));
  623.                 mol.addBond(i,num,1);
  624.                 score[iang]=-1; score[(iang+1)%360]=-1; score[(iang+359)%360]=-1;
  625.                 int i0=(iang+180)%360,i1=(iang+120)%360,i2=(iang+240)%360;
  626.                 if (score[i0]>=0) score[i0]++;
  627.                 if (score[i1]>=0) score[i1]+=2;
  628.                 if (score[i2]>=0) score[i2]+=2;
  629.                 hy--;
  630.             }
  631.            
  632.             mol.setAtomHExplicit(i,Molecule.HEXPLICIT_UNKNOWN);
  633.         }
  634.    
  635.         clearTemporary();
  636.         determineSize();
  637.         repaint();
  638.  
  639.         checkDirtiness();
  640.     }
  641.    
  642.     // of all the selected atoms and their neighbours, removes any which are element H
  643.     public void hydrogenDeleteActual()
  644.     {
  645.         ArrayList<Integer> sel=selectedIndices(),chop=new ArrayList<Integer>();
  646.         for (int n=0;n<sel.size();n++)
  647.         {
  648.             int i=sel.get(n).intValue();
  649.             if (mol.atomElement(i).compareTo("H")==0) chop.add(new Integer(i));
  650.             int adj[]=mol.atomAdjList(i);
  651.             for (int j=0;j<adj.length;j++) if (mol.atomElement(adj[j]).compareTo("H")==0) chop.add(adj[j]);
  652.         }
  653.        
  654.         if (chop.size()==0) return;
  655.         cacheUndo();
  656.         Collections.sort(chop);
  657.        
  658.         for (int n=0;n<chop.size();n++)
  659.         {
  660.             int adj[]=mol.atomAdjList(chop.get(n).intValue());
  661.             for (int i=0;i<adj.length;i++) mol.setAtomHExplicit(adj[i],Molecule.HEXPLICIT_UNKNOWN);
  662.         }
  663.  
  664.         int decr=0,lastVal=-1;
  665.         for (int n=0;n<chop.size();n++)
  666.         {
  667.             int i=chop.get(n).intValue();
  668.             if (i==lastVal) continue;
  669.             mol.deleteAtomAndBonds(i-decr);
  670.             decr++;
  671.             lastVal=i;
  672.         }
  673.    
  674.         clearTemporary();
  675.         determineSize();
  676.         repaint();
  677.  
  678.         checkDirtiness();
  679.     }
  680.    
  681.     // scale bond lengths to reasonable values (note: affects all atoms, selected or not)
  682.     public void normaliseBondLengths()
  683.     {
  684.         double numer=0,denom=0;
  685.         for (int n=1;n<=mol.numBonds();n++)
  686.         {
  687.             double dx=mol.atomX(mol.bondFrom(n))-mol.atomX(mol.bondTo(n)),dy=mol.atomY(mol.bondFrom(n))-mol.atomY(mol.bondTo(n));
  688.             double weight=mol.bondInRing(n) ? 1 : 2;
  689.             numer+=Math.sqrt(dx*dx+dy*dy)*weight;
  690.             denom+=weight;
  691.         }
  692.         if (denom==0) return;
  693.        
  694.         cacheUndo();
  695.        
  696.         double stretch=IDEALBOND*denom/numer;
  697.         for (int n=1;n<=mol.numAtoms();n++)
  698.         {
  699.             mol.setAtomPos(n,mol.atomX(n)*stretch,mol.atomY(n)*stretch);
  700.         }
  701.  
  702.         clearTemporary();
  703.         determineSize();
  704.         repaint();
  705.     }
  706.    
  707.     // select next/prev atoms or connected components
  708.     public void cycleSelection(boolean Forward,boolean Group)
  709.     {
  710.         if (mol.numAtoms()<=1) return;
  711.        
  712.         int high=0;
  713.         if (selected!=null) for (int n=1;n<=mol.numAtoms();n++) if (selected[n-1])
  714.         {
  715.             if (Group) {if (mol.atomConnComp(n)>high) high=mol.atomConnComp(n);}
  716.             else {high=n;}
  717.         }
  718.         int max=Group ? 0 : mol.numAtoms();
  719.         if (Group) for (int n=1;n<=mol.numAtoms();n++) if (mol.atomConnComp(n)>max) max=mol.atomConnComp(n);
  720.        
  721.         int pos=Forward ? high+1 : high-1;
  722.         if (pos<1) pos=max;
  723.         if (pos>max) pos=1;
  724.        
  725.         selected=new boolean[mol.numAtoms()];
  726.         for (int n=1;n<=mol.numAtoms();n++)
  727.         {
  728.             if (Group) {selected[n-1]=mol.atomConnComp(n)==pos;}
  729.             else {selected[n-1]=n==pos;}
  730.         }
  731.        
  732.         clearTemporary(false);
  733.         repaint();
  734.     }
  735.    
  736.     // move selected atoms by a small translation
  737.     public void nudgeSelectedAtoms(double DX,double DY)
  738.     {
  739.         if (selected==null) return;
  740.         cacheUndo();   
  741.         for (int n=1;n<=mol.numAtoms();n++) if (selected[n-1]) mol.setAtomPos(n,mol.atomX(n)+DX,mol.atomY(n)+DY);
  742.  
  743.         clearTemporary(false);
  744.         determineSize();
  745.         repaint();
  746.     }
  747.    
  748.     // selected atoms are inverted about a mirror plane coincident with their centre of gravity
  749.     public void flipSelectedAtoms(boolean Vertical)
  750.     {
  751.         if (selected==null) return;
  752.        
  753.         int count=0;
  754.         double cx=0,cy=0;
  755.         for (int n=1;n<=mol.numAtoms();n++) if (selected[n-1]) {cx+=mol.atomX(n); cy+=mol.atomY(n); count++;}
  756.         if (count==0) return;
  757.        
  758.         cacheUndo();   
  759.  
  760.         cx/=count;
  761.         cy/=count;
  762.         for (int n=1;n<=mol.numAtoms();n++) if (selected[n-1])
  763.         {
  764.             if (Vertical) mol.setAtomPos(n,mol.atomX(n),2*cy-mol.atomY(n));
  765.             else mol.setAtomPos(n,2*cx-mol.atomX(n),mol.atomY(n));
  766.         }
  767.        
  768.         clearTemporary(false);
  769.         determineSize();
  770.         repaint();
  771.     }
  772.    
  773.     // selected atoms are rotated about their centre of gravity
  774.     public void rotateSelectedAtoms(double Degrees)
  775.     {
  776.         if (selected==null) return;
  777.        
  778.         int count=0;
  779.         double cx=0,cy=0;
  780.         for (int n=1;n<=mol.numAtoms();n++) if (selected[n-1]) {cx+=mol.atomX(n); cy+=mol.atomY(n); count++;}
  781.         if (count==0) return;
  782.        
  783.         cacheUndo();   
  784.  
  785.         cx/=count;
  786.         cy/=count;
  787.         double radians=Degrees*Math.PI/180;
  788.         for (int n=1;n<=mol.numAtoms();n++) if (selected[n-1])
  789.         {
  790.             double dx=mol.atomX(n)-cx,dy=mol.atomY(n)-cy;
  791.             double dist=Math.sqrt(dx*dx+dy*dy),theta=Math.atan2(dy,dx);
  792.             mol.setAtomPos(n,cx+dist*Math.cos(theta+radians),cy+dist*Math.sin(theta+radians));
  793.         }
  794.  
  795.         clearTemporary(false);
  796.         determineSize();
  797.         repaint();
  798.     }
  799.    
  800.    
  801.     // changes stereochemistry; STEREO_UNKNOWN=invert, POS/NEG=set to this
  802.     public void setStereo(int Operation)
  803.     {
  804.         ArrayList<Integer> selidx=selectedIndices();
  805.  
  806.         int[][] graph=new int[mol.numAtoms()][];
  807.         for (int n=0;n<mol.numAtoms();n++) graph[n]=mol.atomAdjList(n+1);
  808.  
  809.         cacheUndo();
  810.  
  811.         // chiral centres
  812.         for (int n=0;n<selidx.size();n++)
  813.         {
  814.             int a=selidx.get(n);
  815.             int ster=mol.atomChirality(a);
  816.             if (Operation==Molecule.STEREO_UNKNOWN)
  817.                 {if (ster!=Molecule.STEREO_POS && ster!=Molecule.STEREO_NEG) continue;}
  818.             else
  819.                 {if (ster==Operation) continue;}
  820.  
  821.             // first the easy option: the atom already has chirality, can just flip all the wedges...
  822.             if (ster==Molecule.STEREO_POS || ster==Molecule.STEREO_NEG)
  823.             {
  824.                 for (int i=1;i<=mol.numBonds();i++)
  825.                     if (mol.bondFrom(i)==a)
  826.                 {
  827.                     if (mol.bondType(i)==Molecule.BONDTYPE_INCLINED) mol.setBondType(i,Molecule.BONDTYPE_DECLINED);
  828.                     else if (mol.bondType(i)==Molecule.BONDTYPE_DECLINED) mol.setBondType(i,Molecule.BONDTYPE_INCLINED);
  829.                 }
  830.                 continue;
  831.             }
  832.            
  833.             // not quite so easy: centre has no current chirality, and a specific enantiomer has been requested
  834.             ArrayList<int[]> perm=wedgeFormations(a,Operation);
  835.             if (perm!=null && perm.size()>0) // if anything available, use best...
  836.             {
  837.                 int[] adj=mol.atomAdjList(a);
  838.                 for (int i=0;i<adj.length;i++)
  839.                 {
  840.                     int j=mol.findBond(a,adj[i]); if (j==0) continue;
  841.                     mol.setBondType(j,perm.get(0)[i]<0 ? Molecule.BONDTYPE_DECLINED
  842.                                     : perm.get(0)[i]>0 ? Molecule.BONDTYPE_INCLINED : Molecule.BONDTYPE_NORMAL);
  843.                     if (mol.bondFrom(j)!=a) mol.setBondFromTo(j,mol.bondTo(j),mol.bondFrom(j));
  844.                 }
  845.             }
  846.         }
  847.                
  848.         // cis/trans
  849.         for (int n=1;n<=mol.numBonds();n++)
  850.         {
  851.             int bf=mol.bondFrom(n),bt=mol.bondTo(n);
  852.             if (mol.bondOrder(n)==2 && selidx.indexOf(bf)<0 && selidx.indexOf(bt)<0) continue;
  853.             int ster=mol.bondStereo(n);
  854.             if ((ster!=Molecule.STEREO_POS && ster!=Molecule.STEREO_NEG) || ster==Operation) continue;
  855.             if (mol.atomRingBlock(bf)!=0 && mol.atomRingBlock(bf)!=mol.atomRingBlock(bt)) continue; // refuse to work with ring alkene
  856.            
  857.             // classify the sides of the X=Y bond by partitioning the component
  858.             int sc1=1,sc2=1,side[]=new int[mol.numAtoms()];
  859.             for (int i=0;i<mol.numAtoms();i++) side[i]=0;
  860.             side[bf-1]=1; side[bt-1]=2;
  861.             while (true)
  862.             {
  863.                 boolean changed=false;
  864.                 for (int i=0;i<mol.numAtoms();i++) if (side[i]==0)
  865.                     for (int j=0;j<graph[i].length;j++) if (side[graph[i][j]-1]!=0)
  866.                 {
  867.                     side[i]=side[graph[i][j]-1];
  868.                     if (side[i]==1) sc1++; else sc2++;
  869.                     changed=true;
  870.                 }
  871.                 if (!changed) break;
  872.             }
  873.             int which=sc1<=sc2 ? 1 : 2;
  874.             double cx=mol.atomX(which==1 ? bf : bt),cy=mol.atomY(which==1 ? bf : bt);
  875.             double axis=Math.atan2(cy-mol.atomY(which==1 ? bt : bf),cx-mol.atomX(which==1 ? bt : bf));
  876.             for (int i=0;i<mol.numAtoms();i++) if (side[i]==which)
  877.             {
  878.                 double dx=mol.atomX(i+1)-cx,dy=mol.atomY(i+1)-cy;
  879.                 double r=Math.sqrt(dx*dx+dy*dy),th=Math.atan2(dy,dx);
  880.                 th=2*axis-th;
  881.                 mol.setAtomPos(i+1,cx+r*Math.cos(th),cy+r*Math.sin(th));
  882.             }
  883.             for (int i=1;i<=mol.numBonds();i++)
  884.                 if (mol.bondType(i)==Molecule.BONDTYPE_INCLINED || mol.bondType(i)==Molecule.BONDTYPE_DECLINED)
  885.                     if (side[mol.bondFrom(i)-1]==which && side[mol.bondTo(i)-1]==which)
  886.                         mol.setBondType(i,mol.bondType(i)==Molecule.BONDTYPE_INCLINED ? Molecule.BONDTYPE_DECLINED
  887.                                                                                       : Molecule.BONDTYPE_INCLINED);
  888.         }
  889.  
  890.         clearTemporary(false);
  891.         determineSize();
  892.         repaint();
  893.     }
  894.    
  895.     // selected chiral centres lose their wedge bonds
  896.     public void removeChiralWedges()
  897.     {
  898.         cacheUndo();
  899.    
  900.         ArrayList<Integer> selidx=selectedIndices();
  901.         for (int n=0;n<selidx.size();n++) if (mol.atomChirality(selidx.get(n))!=Molecule.STEREO_NONE)
  902.         {
  903.             for (int i=1;i<=mol.numBonds();i++)
  904.                 if ((mol.bondFrom(i)==selidx.get(n) || mol.bondTo(i)==selidx.get(n)) &&
  905.                     (mol.bondType(i)==Molecule.BONDTYPE_INCLINED || mol.bondType(i)==Molecule.BONDTYPE_DECLINED))
  906.                 mol.setBondType(i,Molecule.BONDTYPE_NORMAL);
  907.         }
  908.         repaint();
  909.     }
  910.  
  911.     // for any chiral centres, pick the next set of valid wedge bonds
  912.     public void cycleChiralWedges()
  913.     {
  914.         cacheUndo();
  915.    
  916.         ArrayList<Integer> selidx=selectedIndices();
  917.         for (int n=0;n<selidx.size();n++)
  918.         {
  919.             int a=selidx.get(n),chi=mol.atomChirality(a);
  920.             if (chi!=Molecule.STEREO_POS && chi!=Molecule.STEREO_NEG) continue;
  921.             ArrayList<int[]> perm=wedgeFormations(a,chi);
  922.             if (perm.size()<=1) continue; // invalid or no point
  923.            
  924.             int[] adj=mol.atomAdjList(a),curperm=new int[adj.length];
  925.             for (int i=0;i<adj.length;i++)
  926.             {
  927.                 int j=mol.findBond(a,adj[i]);
  928.                 curperm[i]=mol.bondType(j)==Molecule.BONDTYPE_INCLINED ? 1 : mol.bondType(j)==Molecule.BONDTYPE_DECLINED ? -1 : 0;
  929.             }
  930.             int match=-1;
  931.             for (int i=0;i<perm.size();i++)
  932.             {
  933.                 int[] thisperm=perm.get(i);
  934.                 boolean same=true;
  935.                 for (int j=0;j<curperm.length;j++) if (thisperm[j]!=curperm[j]) {same=false; break;}
  936.                 if (same) {match=i; break;}
  937.             }
  938.             match=(match+1)%perm.size();
  939.             curperm=perm.get(match);
  940.  
  941.             for (int i=0;i<adj.length;i++)
  942.             {
  943.                 int j=mol.findBond(a,adj[i]);
  944.                 if (mol.bondFrom(j)!=a) mol.setBondFromTo(j,a,adj[i]);
  945.                 mol.setBondType(j,curperm[i]<0 ? Molecule.BONDTYPE_DECLINED
  946.                                 : curperm[i]>0 ? Molecule.BONDTYPE_INCLINED : Molecule.BONDTYPE_NORMAL);
  947.             }
  948.         }
  949.         repaint();
  950.     }
  951.    
  952.     // ------------------ private functions --------------------
  953.  
  954.     // translation of screen & molecule coordinates    
  955.     double angToX(double AX) {return (offsetX+AX)*scale;}
  956.     double angToY(double AY) {return (offsetY-AY)*scale;}
  957.     double xToAng(double PX) {return (PX/scale)-offsetX;}
  958.     double yToAng(double PY) {return (-PY/scale)+offsetY;}
  959.  
  960.     // resizes the widget, which is assumed scrollable, to fit the current magnification of the whole molecule
  961.     void determineSize()
  962.     {
  963.         int w,h;
  964.         if (!isSelectionPane)
  965.         {
  966.             w=Math.max((int)angToX(mol.maxX()+1),500);
  967.             h=Math.max((int)angToY(mol.minY()-1),500);
  968.         }
  969.         else
  970.         {
  971.             w=selBoxW;
  972.             h=selBoxH;
  973.         }
  974.         setPreferredSize(new Dimension(w,h));
  975.         setSize(w,h);
  976.     }
  977.    
  978.     // erases some of the datastructures used for caching the drawing elements
  979.     void clearTemporary() {clearTemporary(true);}
  980.     void clearTemporary(boolean AndSelected)
  981.     {
  982.         px=py=rw=rh=null;
  983.         highlightAtom=highlightBond=0;
  984.         if (AndSelected) selected=null;
  985.         else if (selected!=null && selected.length!=mol.numAtoms())
  986.         {
  987.             boolean newSelected[]=new boolean[mol.numAtoms()];
  988.             for (int n=0;n<selected.length;n++) newSelected[n]=selected[n];
  989.             selected=newSelected;
  990.         }
  991.     }
  992.    
  993.     void resetSelected(boolean Clear)
  994.     {
  995.         if (selected==null) selected=new boolean[mol.numAtoms()];
  996.         if (Clear) for (int n=0;n<mol.numAtoms();n++) selected[n]=false;
  997.     }
  998.    
  999.     // return the atom underneath the given position, in screen coordinates; assumes that the appropriate arrays of size and position
  1000.     // have been filled out
  1001.     private int pickAtom(int X,int Y)
  1002.     {
  1003.         if (px==null || py==null) return 0; //DefinePositions()...?;
  1004.        
  1005.         for (int n=1;n<=mol.numAtoms();n++)
  1006.         {
  1007.             double dx=X-px[n-1],dy=Y-py[n-1];
  1008.             if (Math.abs(dx)<=rw[n-1] && Math.abs(dy)<=rh[n-1])
  1009.                 if (dx*dx/(rw[n-1]*rw[n-1])+dy*dy/(rh[n-1]*rh[n-1])<=1) {return n;}
  1010.         }
  1011.         return 0;
  1012.     }
  1013.    
  1014.     // returns the bond underneath the screen position
  1015.     private int pickBond(int X,int Y)
  1016.     {
  1017.         if (px==null || py==null) return 0;
  1018.    
  1019.         for (int n=1;n<=mol.numBonds();n++)
  1020.         {
  1021.             double x1=px[mol.bondFrom(n)-1],y1=py[mol.bondFrom(n)-1],x2=px[mol.bondTo(n)-1],y2=py[mol.bondTo(n)-1];
  1022.  
  1023.             double nx1=x1,ny1=y1,nx2=x2,ny2=y2;
  1024.             int delta=Math.max(2,(int)(scale/20));
  1025.             if (nx1>nx2) {nx1=x2; nx2=x1;}
  1026.             if (ny1>ny2) {ny1=y2; ny2=y1;}
  1027.             if (X<nx1-2*delta || X>nx2+2*delta || Y<ny1-2*delta || Y>ny2+2*delta) continue;
  1028.  
  1029.             double dx=x2-x1,dy=y2-y1,d;
  1030.             if (Math.abs(dx)>Math.abs(dy)) d=Y-y1-(X-x1)*dy/dx; else d=X-x1-(Y-y1)*dx/dy;
  1031.             if (Math.abs(d)>(2+mol.bondOrder(n))*delta) continue;
  1032.             return n;
  1033.         }
  1034.         return 0;
  1035.     }
  1036.    
  1037.     // snaps the draw-to-position to multiples of 30 degrees
  1038.     private void snapToolBond()
  1039.     {
  1040.         double cx=toolBondFrom>0 ? mol.atomX(toolBondFrom) : toolBondFromX;
  1041.         double cy=toolBondFrom>0 ? mol.atomY(toolBondFrom) : toolBondFromY;
  1042.         double dx=toolBondToX-cx,dy=toolBondToY-cy;
  1043.         double th=Math.atan2(dy,dx)*180/Math.PI,ext=Math.sqrt(dx*dx+dy*dy);
  1044.         th=(Math.round(th/30)*30)*Math.PI/180;
  1045.         ext=Math.round(ext/IDEALBOND)*IDEALBOND;
  1046.         toolBondToX=cx+ext*Math.cos(th);
  1047.         toolBondToY=cy+ext*Math.sin(th);
  1048.     }
  1049.        
  1050.     // should be called before any unit operation is conducted; the current molecule state is stored in the undo buffer
  1051.     public void cacheUndo()
  1052.     {
  1053.         // !! check to see if the last molecule in the cache is literally identical, and if so, do nothing
  1054.         if (undo==null) undo=new EditState[UNDO_LEVELS];
  1055.         redo=null;
  1056.         for (int n=UNDO_LEVELS-1;n>0;n--) undo[n]=undo[n-1];
  1057.         undo[0]=new EditState();
  1058.         undo[0].Molecule=mol==null ? null : mol.clone();
  1059.         undo[0].Selected=selected==null ? null : selected.clone();
  1060.     }
  1061.    
  1062.     // called when the element editing widget has ended its lifecycle, and the change is to be applied
  1063.     private void completeAtomEdit()
  1064.     {
  1065.         if (toolAtomEditBox==null) return;
  1066.         String el=toolAtomEditBox.getText();
  1067.         if (el.length()>0)
  1068.         {
  1069.             cacheUndo();
  1070.  
  1071.             if (el.charAt(0)>='a' && el.charAt(0)<='z') el=el.substring(0,1).toUpperCase()+el.substring(1);
  1072.  
  1073.             if (toolAtomEditSel==0)
  1074.             {
  1075.                 mol.addAtom(el,xToAng(toolAtomEditX),yToAng(toolAtomEditY));
  1076.                 clearTemporary();
  1077.                 determineSize();
  1078.             }
  1079.             else mol.setAtomElement(toolAtomEditSel,el);
  1080.         }
  1081.        
  1082.         toolAtomEditBox.setVisible(false);
  1083.         remove(toolAtomEditBox);
  1084.         toolAtomEditBox=null;
  1085.        
  1086.         repaint();
  1087.         checkDirtiness();
  1088.     }
  1089.    
  1090.     // the currently active template is rotated according to a mapping between atoms
  1091.     private void adjustTemplateByAtom(int Atom)
  1092.     {
  1093.         templDraw=template.clone();
  1094.        
  1095.         ArrayList<Integer> bonded=new ArrayList<Integer>();
  1096.         for (int n=1;n<=mol.numBonds();n++)
  1097.         {
  1098.             if (mol.bondFrom(n)==Atom) bonded.add(new Integer(mol.bondTo(n)));
  1099.             if (mol.bondTo(n)==Atom) bonded.add(new Integer(mol.bondFrom(n)));
  1100.         }
  1101.        
  1102.         final int INCR=1;
  1103.         double[] rotScores=new double[360/INCR];
  1104.         for (int n=1;n<=templDraw.numAtoms();n++) if (n!=templateIdx)
  1105.         {
  1106.             double x=template.atomX(n)-template.atomX(templateIdx),y=template.atomY(n)-template.atomY(templateIdx);
  1107.             double th=Math.atan2(y,x),ext=Math.sqrt(x*x+y*y);
  1108.             for (int i=0;i<(360/INCR);i++)
  1109.             {
  1110.                 double rx=mol.atomX(Atom)+ext*Math.cos(th+i*INCR*Math.PI/180),ry=mol.atomY(Atom)+ext*Math.sin(th+i*INCR*Math.PI/180);
  1111.                 for (int j=0;j<bonded.size();j++)
  1112.                 {
  1113.                     int k=bonded.get(j).intValue();
  1114.                     double dx=mol.atomX(k)-rx,dy=mol.atomY(k)-ry;
  1115.                     double ext2=dx*dx+dy*dy;
  1116.                     if (ext2<0.01) ext2=0.01;
  1117.                     rotScores[i]+=1/ext2;
  1118.                 }
  1119.             }
  1120.         }
  1121.        
  1122.         int bestRot=0;
  1123.         for (int n=1;n<(360/INCR);n++) if (rotScores[n]<rotScores[bestRot]) bestRot=n;
  1124.        
  1125.         for (int n=1;n<=templDraw.numAtoms();n++)
  1126.         {
  1127.             double x=template.atomX(n)-template.atomX(templateIdx),y=template.atomY(n)-template.atomY(templateIdx);
  1128.             double th=Math.atan2(y,x),ext=Math.sqrt(x*x+y*y);
  1129.             templDraw.setAtomPos(n,mol.atomX(Atom)+ext*Math.cos(th+bestRot*INCR*Math.PI/180),
  1130.                                    mol.atomY(Atom)+ext*Math.sin(th+bestRot*INCR*Math.PI/180));
  1131.         }
  1132.     }
  1133.    
  1134.     // the currently active template is rotated according to a mapping between bonds
  1135.     private boolean adjustTemplateByBond(int Bond)
  1136.     {
  1137.         Molecule[] rotMol=new Molecule[2];
  1138.         double[] rotScores=new double[2];
  1139.        
  1140.         for (int r=0;r<2;r++)
  1141.         {
  1142.             rotMol[r]=template.clone();
  1143.             int imol1=r==0 ? mol.bondFrom(Bond) : mol.bondTo(Bond),imol2=r==0 ? mol.bondTo(Bond) : mol.bondFrom(Bond);
  1144.             int irot1=template.bondFrom(-templateIdx),irot2=template.bondTo(-templateIdx);
  1145.             double dtheta=Math.atan2(mol.atomY(imol2)-mol.atomY(imol1),mol.atomX(imol2)-mol.atomX(imol1))
  1146.                          -Math.atan2(template.atomY(irot2)-template.atomY(irot1),template.atomX(irot2)-template.atomX(irot1));
  1147.            
  1148.             for (int n=1;n<=template.numAtoms();n++)
  1149.             {
  1150.                 double rx=template.atomX(n)-template.atomX(irot1),ry=template.atomY(n)-template.atomY(irot1);
  1151.                 double th=Math.atan2(ry,rx),ext=Math.sqrt(rx*rx+ry*ry);
  1152.                 rx=mol.atomX(imol1)+ext*Math.cos(th+dtheta);
  1153.                 ry=mol.atomY(imol1)+ext*Math.sin(th+dtheta);
  1154.                 rotMol[r].setAtomPos(n,rx,ry);
  1155.                
  1156.                 for (int i=1;i<=mol.numAtoms();i++)
  1157.                 {
  1158.                     double dx=mol.atomX(i)-rx,dy=mol.atomY(i)-ry;
  1159.                     double ext2=dx*dx+dy*dy;
  1160.                     if (ext2<0.01) ext2=0.01;
  1161.                     rotScores[r]+=1/ext2;
  1162.                 }
  1163.             }
  1164.         }
  1165.        
  1166.         boolean swap=rotScores[0]<rotScores[1];
  1167.         templDraw=rotMol[swap ? 0 : 1];
  1168.         return swap;
  1169.     }
  1170.    
  1171.     // the currently active template is merely translated, as there is no current atom or bond mapping
  1172.     private void adjustTemplateByCoord(double X,double Y)
  1173.     {
  1174.         templDraw=template.clone();
  1175.  
  1176.         double dx=0,dy=0;
  1177.         if (templateIdx>0) {dx=template.atomX(templateIdx); dy=template.atomY(templateIdx);}
  1178.         else if (templateIdx<0)
  1179.         {
  1180.             int from=template.bondFrom(-templateIdx),to=template.bondTo(-templateIdx);
  1181.             dx=0.5*(template.atomX(from)+template.atomX(to));
  1182.             dy=0.5*(template.atomY(from)+template.atomY(to));
  1183.         }
  1184.         for (int n=1;n<=template.numAtoms();n++) templDraw.setAtomPos(n,template.atomX(n)-dx+X,template.atomY(n)-dy+Y);
  1185.     }
  1186.    
  1187.     // places a template, where atoms are mapped
  1188.     private void templateSetByAtom(int JoinAtom)
  1189.     {
  1190.         int[] map=new int[templDraw.numAtoms()];
  1191.         int oldNum=mol.numAtoms();
  1192.         for (int n=1;n<=templDraw.numAtoms();n++) if (JoinAtom==0 || n!=templateIdx)
  1193.         {
  1194.             mol.addAtom(templDraw.atomElement(n),templDraw.atomX(n),templDraw.atomY(n),
  1195.                         templDraw.atomCharge(n),templDraw.atomUnpaired(n));
  1196.         }
  1197.         for (int n=1;n<=templDraw.numBonds();n++)
  1198.         {
  1199.             int from=templDraw.bondFrom(n);
  1200.             int to=templDraw.bondTo(n);
  1201.             if (JoinAtom>0)
  1202.             {
  1203.                 if (from==templateIdx) from=JoinAtom;
  1204.                 else
  1205.                 {
  1206.                     if (from>templateIdx) from--;
  1207.                     from+=oldNum;
  1208.                 }
  1209.                 if (to==templateIdx) to=JoinAtom;
  1210.                 else
  1211.                 {
  1212.                     if (to>templateIdx) to--;
  1213.                     to+=oldNum;
  1214.                 }
  1215.             }
  1216.             else {from+=oldNum; to+=oldNum;}
  1217.             mol.addBond(from,to,templDraw.bondOrder(n),templDraw.bondType(n));
  1218.         }
  1219.  
  1220.         mergeNewAtoms(oldNum);
  1221.  
  1222.         clearTemporary();
  1223.         determineSize();
  1224.         repaint();
  1225.     }
  1226.    
  1227.     // places a template, where bonds are mapped
  1228.     private void templateSetByBond(int JoinBond,boolean Swap)
  1229.     {
  1230.         int[] map=new int[templDraw.numAtoms()];
  1231.         int oldNum=mol.numAtoms();
  1232.         int joinFrom=JoinBond>0 ? mol.bondFrom(JoinBond) : 0,joinTo=JoinBond>0 ? mol.bondTo(JoinBond) : 0;
  1233.         int newFrom=Swap ? templDraw.bondFrom(-templateIdx) : templDraw.bondTo(-templateIdx);
  1234.         int newTo=Swap ? templDraw.bondTo(-templateIdx) : templDraw.bondFrom(-templateIdx);    
  1235.         for (int n=1;n<=templDraw.numAtoms();n++)
  1236.         {
  1237.             if (n==newFrom && JoinBond>0) map[n-1]=joinFrom;
  1238.             else if (n==newTo && JoinBond>0) map[n-1]=joinTo;
  1239.             else
  1240.             {
  1241.                 map[n-1]=mol.addAtom(templDraw.atomElement(n),templDraw.atomX(n),templDraw.atomY(n),
  1242.                                      templDraw.atomCharge(n),templDraw.atomUnpaired(n));
  1243.             }
  1244.         }
  1245.         for (int n=1;n<=template.numBonds();n++) if (n!=-templateIdx || JoinBond==0)
  1246.         {
  1247.             mol.addBond(map[templDraw.bondFrom(n)-1],map[templDraw.bondTo(n)-1],templDraw.bondOrder(n),templDraw.bondType(n));
  1248.         }
  1249.        
  1250.         mergeNewAtoms(oldNum);
  1251.        
  1252.         clearTemporary();
  1253.         determineSize();
  1254.         repaint();
  1255.     }
  1256.    
  1257.     // any atoms of index greater than the watermark are merged with previously defined atoms if they are close
  1258.     private void mergeNewAtoms(int Watermark)
  1259.     {
  1260.         int pos=Watermark+1;
  1261.         while (pos<=mol.numAtoms())
  1262.         {
  1263.             int close=0;
  1264.             for (int n=1;n<=Watermark;n++)
  1265.             {
  1266.                 double dx=mol.atomX(n)-mol.atomX(pos),dy=mol.atomY(n)-mol.atomY(pos);
  1267.                 if (dx*dx+dy*dy<0.2*0.2) {close=n; break;}
  1268.             }
  1269.             if (close>0)
  1270.             {
  1271.                 int[] adj=mol.atomAdjList(pos);
  1272.                 for (int i=0;i<adj.length;i++) if (mol.findBond(close,adj[i])==0)
  1273.                 {
  1274.                     int j=mol.findBond(pos,adj[i]);
  1275.                     mol.addBond(close,adj[i],mol.bondOrder(j));
  1276.                 }
  1277.                 mol.deleteAtomAndBonds(pos);
  1278.             }
  1279.             else pos++;
  1280.         }
  1281.     }
  1282.  
  1283.     // returns true if there are any selected atoms
  1284.     private boolean anySelected()
  1285.     {
  1286.         if (selected==null) return false;
  1287.         for (int n=0;n<mol.numAtoms();n++) if (selected[n]) return true;
  1288.         return false;
  1289.     }
  1290.  
  1291.     private double dragExtendBy(double px,double py)
  1292.     {
  1293.         double diff=0.2*Math.sqrt(px*px+py*py)/scale;
  1294.         if (px<0 && py<0) diff=-diff;
  1295.        
  1296.         if (diff>=0) return 1+diff;
  1297.         else return Math.exp(diff);
  1298.     }
  1299.    
  1300.     // calculate all the wedge bond formations for a given atom for a given chirality (+/-), ranked in order, null if none
  1301.     private ArrayList<int[]> wedgeFormations(int N,int Chi)
  1302.     {
  1303.         if (mol.atomAdjCount(N)!=3 && mol.atomAdjCount(N)!=4) return null;
  1304.         int[] adj=mol.atomAdjList(N);
  1305.         for (int i=0;i<adj.length-1;i++) for (int j=i+1;j<adj.length;j++)
  1306.             if (mol.atomPriority(adj[i])==mol.atomPriority(adj[j])) return null;
  1307.  
  1308.         int[] badj=new int[adj.length];
  1309.         for (int n=0;n<adj.length;n++) badj[n]=mol.findBond(N,adj[n]);
  1310.        
  1311.         ArrayList<int[]> perm=new ArrayList<int[]>();
  1312.  
  1313.         // generate all possible sensible wedge combinations
  1314.         if (adj.length==3)
  1315.         {
  1316.             for (int i=0;i<3;i++) for (int iz=-1;iz<=1;iz+=2)
  1317.             {
  1318.                 int[] wedges=new int[3];
  1319.                 for (int n=0;n<3;n++) wedges[n]=0;
  1320.                 wedges[i]=iz;
  1321.                 perm.add(wedges);
  1322.             }
  1323.         }
  1324.         else
  1325.         {
  1326.             for (int i=0;i<4;i++) for (int iz=-1;iz<=1;iz+=2)
  1327.             {
  1328.                 int[] wedges=new int[4];
  1329.                 for (int n=0;n<4;n++) wedges[n]=0;
  1330.                 wedges[i]=iz;
  1331.                 perm.add(wedges);
  1332.                
  1333.                 for (int j=i+1;j<4;j++) for (int jz=-1;jz<=1;jz+=2)
  1334.                 {
  1335.                     if (/*i==j || */jz==iz) continue;
  1336.                     wedges=new int[4];
  1337.                     for (int n=0;n<4;n++) wedges[n]=0;
  1338.                     wedges[i]=iz;
  1339.                     wedges[j]=jz;
  1340.                     perm.add(wedges);
  1341.                 }
  1342.             }
  1343.         }
  1344.  
  1345.         // keep only the ones which indicate the desired enantiomer
  1346.         int pos=0;
  1347.         while (pos<perm.size())
  1348.         {
  1349.             int[] wedges=perm.get(pos);
  1350.             Molecule mchi=mol.clone();
  1351.             for (int n=0;n<adj.length;n++)
  1352.             {
  1353.                 mchi.setBondType(badj[n],wedges[n]<0 ? Molecule.BONDTYPE_DECLINED
  1354.                                        : wedges[n]>0 ? Molecule.BONDTYPE_INCLINED : Molecule.BONDTYPE_NORMAL);
  1355.                 if (mchi.bondFrom(badj[n])!=N) mol.setBondFromTo(badj[n],mol.bondTo(badj[n]),mol.bondFrom(badj[n]));
  1356.  
  1357.             }
  1358.             if (mchi.atomChirality(N)!=Chi) perm.remove(pos); else pos++;
  1359.         }
  1360.        
  1361.         // score each one based on crude aesthetic criteria
  1362.         double[] score=new double[perm.size()];
  1363.         for (int n=0;n<perm.size();n++)
  1364.         {
  1365.             score[n]=0;
  1366.             int[] wedges=perm.get(n);
  1367.             int wcount=0;
  1368.             for (int i=0;i<adj.length;i++) if (wedges[i]!=0)
  1369.             {
  1370.                 wcount++;
  1371.                 score[n]-=0.5*mol.atomPriority(adj[i])/mol.numAtoms();
  1372.                 if (mol.atomAdjCount(adj[i])==1) score[n]++;
  1373.                 if (mol.atomRingBlock(adj[i])>0)
  1374.                 {
  1375.                     score[n]--;
  1376.                     if (mol.atomRingBlock(N)==mol.atomRingBlock(adj[i])) score[n]--;
  1377.                 }
  1378.             }
  1379.             if (adj.length==4 && wcount==2) score[n]++;
  1380.         }
  1381.        
  1382.         // sort best-first
  1383.         pos=0;
  1384.         while (pos<perm.size()-1)
  1385.         {
  1386.             if (score[pos]<score[pos+1])
  1387.             {
  1388.                 int[] w1=perm.get(pos),w2=perm.get(pos+1);
  1389.                 perm.set(pos+1,w1);
  1390.                 perm.set(pos,w2);
  1391.                 double s=score[pos]; score[pos]=score[pos+1]; score[pos+1]=s;
  1392.                 if (pos>0) pos--;
  1393.             } else pos++;
  1394.         }
  1395.        
  1396.        
  1397.         /*System.out.println("PERMSIZE:"+perm.size());
  1398.         for (int n=0;n<perm.size();n++)
  1399.         {
  1400.             int[] w=perm.get(n);
  1401.             System.out.print("n="+n);
  1402.             for (int i=0;i<w.length;i++) System.out.print(" "+w[i]);
  1403.             System.out.println(" score="+score[n]);
  1404.         }*/
  1405.  
  1406.         return perm;
  1407.     }
  1408.    
  1409.     // ------------------ event functions --------------------
  1410.    
  1411.     // the paint function sets up the basic graphics drawing context, then performs some preliminary transformations of any tools effects
  1412.     // that are in progress, hands off all the work to DrawMolecule, then fetches back some of data determined in situ
  1413.    
  1414.     protected void paintComponent(Graphics gr)
  1415.     {
  1416.         if (autoScale) scaleToFit();
  1417.    
  1418.         gr.setColor(Color.WHITE);
  1419.         gr.fillRect(0,0,getWidth(),getHeight());
  1420.         if (hasBorder)
  1421.         {
  1422.             gr.setColor(Color.BLACK);
  1423.             gr.drawRect(0,0,getWidth()-1,getHeight()-1);
  1424.         }
  1425.  
  1426.         Graphics2D g=(Graphics2D)gr;
  1427.         g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
  1428.         DrawMolecule draw=new DrawMolecule(mol,g,scale);
  1429.        
  1430.         // provide a miscellany of editing data to the drawing class, so that it can represent everything that is going on
  1431.        
  1432.         draw.setBackground(getBackground());
  1433.         draw.setShowHydr(showHydr);
  1434.         draw.setShowMode(showMode);
  1435.         draw.setShowStereo(showSter);
  1436.         draw.setOffset(offsetX,offsetY);
  1437.         draw.setHighlight(highlightAtom,highlightBond);
  1438.        
  1439.         resetSelected(false);
  1440.         draw.setSelected(selected,dragged);
  1441.  
  1442.         if ((tool==TOOL_ATOM && toolAtomDrag) || (tool==TOOL_BOND && toolBondFrom>0))
  1443.             draw.bondInProgress(toolBondFrom,toolBondToX,toolBondToY,toolBondOrder,toolBondType);
  1444.         if (tool==TOOL_ATOM && toolAtomDrag && toolAtomType!=null && toolAtomType.compareTo("C")!=0)
  1445.             draw.atomInProgress(toolAtomType,toolBondToX,toolBondToY);
  1446.         if (tool==TOOL_BOND && toolBondFrom==0 && toolBondDrag)
  1447.         {
  1448.             int i=pickAtom((int)angToX(toolBondToX),(int)angToY(toolBondToY));
  1449.             if (i==0 && toolSnap) snapToolBond();
  1450.             double x1=toolBondFromX,y1=toolBondFromY,x2=toolBondToX,y2=toolBondToY;
  1451.             if (i>0) {x2=mol.atomX(i); y2=mol.atomY(i);}
  1452.             draw.newBondLine(x1,y1,x2,y2);
  1453.         }
  1454.         if (toolDragReason==DRAG_SELECT)
  1455.         {
  1456.             draw.dragSelect((int)toolDragX1,(int)toolDragY1,(int)toolDragX2,(int)toolDragY2);
  1457.         }
  1458.         if ((toolDragReason==DRAG_MOVE || toolDragReason==DRAG_COPY || toolDragReason==DRAG_SCALE)
  1459.             && (toolDragX1!=toolDragX2 || toolDragY1!=toolDragY2))
  1460.         {
  1461.             if (toolDragReason==DRAG_SCALE)
  1462.             {
  1463.                 double extmul=dragExtendBy(toolDragX2-toolDragX1,toolDragY2-toolDragY1),cx=0,cy=0;
  1464.                 int count=0;
  1465.                 for (int n=1;n<=mol.numAtoms();n++) if (selected[n-1]) {cx+=mol.atomX(n); cy+=mol.atomY(n); count++;}
  1466.                 cx/=count; cy/=count;
  1467.                 draw.dragScale(cx,cy,extmul);
  1468.             }
  1469.             else
  1470.             {
  1471.                 int dx=(int)(toolDragX2-toolDragX1),dy=(int)(toolDragY2-toolDragY1);
  1472.                 draw.dragMove(dx,dy,toolDragReason==DRAG_COPY);
  1473.             }
  1474.         }
  1475.         if (toolDragReason==DRAG_ROTATE && (Math.abs(toolDragX2-toolDragX1)>5 || Math.abs(toolDragY2-toolDragY1)>5))
  1476.         {
  1477.             double dx=toolDragX2-toolDragX1,dy=toolDragY2-toolDragY1;
  1478.             double th=-Math.atan2(dy,dx)*180/Math.PI;
  1479.             if (toolSnap) th=Math.round(th/15)*15;
  1480.            
  1481.             draw.dragRotate(th,(int)toolDragX1,(int)toolDragY1);
  1482.         }
  1483.         if (tool==TOOL_TEMPLATE && trackX>=0 && trackY>=0)
  1484.         {
  1485.             if (highlightAtom!=0 && templateIdx>0) adjustTemplateByAtom(highlightAtom);
  1486.             else if (highlightBond!=0 && templateIdx<0) adjustTemplateByBond(highlightBond);
  1487.             else adjustTemplateByCoord(xToAng(trackX),yToAng(trackY));
  1488.  
  1489.             draw.outlineTemplate(templDraw);
  1490.         }
  1491.  
  1492.         draw.draw();
  1493.  
  1494.         // fetch some of the calculated properties, which are used for the editing progress
  1495.         ArrangeMolecule arrmol=draw.arrangement();
  1496.         px=new double[mol.numAtoms()];
  1497.         py=new double[mol.numAtoms()];
  1498.         rw=new double[mol.numAtoms()];
  1499.         rh=new double[mol.numAtoms()];
  1500.         for (int n=0;n<mol.numAtoms();n++)
  1501.         {
  1502.             px[n]=arrmol.pointCX(n);
  1503.             py[n]=arrmol.pointCY(n);
  1504.             rw[n]=Math.max(arrmol.pointRW(n),5);
  1505.             rh[n]=Math.max(arrmol.pointRH(n),5);
  1506.         }
  1507.     }
  1508.    
  1509.     // Mouse events: the callbacks for Clicked, Pressed, Released, Dragged and Moved form a slightly complicated interplay of the various
  1510.     // tool events. The 'tool' variable, and its various permitted values, should make most of the behaviour reasonably self-explanatory;
  1511.     // note that many of the tools have multiple functions which may be sprinkled around the various event callbacks.
  1512.    
  1513.     public void mouseClicked(MouseEvent e)
  1514.     {
  1515.     //jm.evers 17/1/2010
  1516.         if( e.getButton() == MouseEvent.BUTTON1 && MainApplet.USER_SELECTION  && tool == TOOL_SELECT  ){
  1517.         //      System.out.println("highlightBond = "+highlightBond);
  1518.              if( highlightAtom > 0){
  1519.                 if(highlightAtom > atomselection.length - 1){
  1520.                     atomselection = GrowArray(atomselection , highlightAtom+2 );
  1521.                 }
  1522.                 /* flip flop...select unselect */
  1523.                 if(atomselection[highlightAtom]){
  1524.                     atomselection[highlightAtom] = false; // de_select
  1525.                 }else{
  1526.                     atomselection[highlightAtom] = true; // select
  1527.                 }
  1528.              }
  1529.              if(highlightBond > 0){
  1530.                 if(highlightBond > bondselection.length - 1){
  1531.                     bondselection = GrowArray(bondselection , highlightBond+2 );
  1532.                 }
  1533.                 /* flip flop...select unselect */
  1534.                 if(bondselection[highlightBond]){
  1535.                     bondselection[highlightBond] = false; // de_select
  1536.                 }else{
  1537.                     bondselection[highlightBond] = true; // select
  1538.                 }
  1539.              }
  1540.             repaint();
  1541.         }
  1542.         else
  1543.         if (tool==TOOL_CURSOR && selectListen!=null)
  1544.         {
  1545.             int i=pickAtom(e.getX(),e.getY());
  1546.             if ((e.getModifiers()&MouseEvent.CTRL_MASK)>0 && i>0 && editable) // select connected component
  1547.             {
  1548.                 if ((e.getModifiers()&MouseEvent.SHIFT_MASK)==0 && selected!=null) for (int n=0;n<mol.numAtoms();n++) selected[n]=false;
  1549.                 if (selected==null) selected=new boolean[mol.numAtoms()];
  1550.                 int cc=mol.atomConnComp(i);
  1551.                 for (int n=1;n<=mol.numAtoms();n++) if (mol.atomConnComp(n)==cc) selected[n-1]=true;
  1552.                 repaint();
  1553.             }
  1554.             else if (i>0) selectListen.molSelected(this,i,e.getClickCount()>1); // notify of atom selection
  1555.             else // notify of bond (or nothing) selection
  1556.             {
  1557.                 i=pickBond(e.getX(),e.getY());
  1558.                 /*if (i>0)*/ selectListen.molSelected(this,-i,e.getClickCount()>1);
  1559.                 // (0==clicked in general area)
  1560.             }
  1561.         }
  1562.         else if (tool==TOOL_ROTATOR) // deselect
  1563.         {
  1564.             selected=null;
  1565.             clearTemporary();
  1566.             repaint();
  1567.         }
  1568.         else if (tool==TOOL_ERASOR) // delete something, be it atom or bond
  1569.         {
  1570.             if(MainApplet.USER_SELECTION){ deSelectAll(); } /* remove markings otherwiser trouble */
  1571.             int i=pickAtom(e.getX(),e.getY());
  1572.             if (i>0)
  1573.             {
  1574.                 cacheUndo();
  1575.                 mol.deleteAtomAndBonds(i);
  1576.             }
  1577.             else
  1578.             {
  1579.                 cacheUndo();
  1580.                 i=pickBond(e.getX(),e.getY());
  1581.                 if (i>0) mol.deleteBond(i);
  1582.             }
  1583.             if (i>0)
  1584.             {
  1585.                 clearTemporary();
  1586.                 determineSize();
  1587.                 repaint();
  1588.             }
  1589.         }
  1590.         else if (tool==TOOL_ATOM && e.getButton()==MouseEvent.BUTTON1 && !toolAtomDrag)
  1591.         {
  1592.             if (toolAtomEditBox!=null)
  1593.             {
  1594.                 completeAtomEdit();
  1595.                 return;
  1596.             }
  1597.        
  1598.             if (toolAtomType!=null)  // add new atom, or change element label
  1599.             {
  1600.                 int i=pickAtom(e.getX(),e.getY());
  1601.                 cacheUndo();
  1602.                 if (i==0)
  1603.                 {
  1604.                     i=mol.addAtom(toolAtomType,xToAng(e.getX()),yToAng(e.getY()));
  1605.                     offsetX=e.getX()/scale-mol.atomX(i);
  1606.                     offsetY=e.getY()/scale+mol.atomY(i);
  1607.                 }
  1608.                 else mol.setAtomElement(i,toolAtomType);
  1609.                 clearTemporary();
  1610.                 determineSize();
  1611.                 repaint();
  1612.             }
  1613.             else // setup new editing box for element
  1614.             {
  1615.                 toolAtomEditX=e.getX(); toolAtomEditY=e.getY();
  1616.                 toolAtomEditSel=pickAtom(toolAtomEditX,toolAtomEditY);
  1617.                 if (toolAtomEditSel==0 && pickBond(e.getX(),e.getY())>0) return;
  1618.                
  1619.                 toolAtomEditBox=new JTextField(toolAtomEditSel>0 ? mol.atomElement(toolAtomEditSel) : "");
  1620.                 add(toolAtomEditBox);
  1621.                 toolAtomEditBox.addFocusListener(this);
  1622.                 toolAtomEditBox.addKeyListener(this);
  1623.                 toolAtomEditBox.setLocation(toolAtomEditX-10,toolAtomEditY-10);
  1624.                 toolAtomEditBox.setSize(20,20);
  1625.                 toolAtomEditBox.setVisible(true);
  1626.                 toolAtomEditBox.setSelectionStart(0);
  1627.                 toolAtomEditBox.setSelectionEnd(toolAtomEditBox.getText().length());
  1628.                 toolAtomEditBox.grabFocus();
  1629.             }
  1630.         }
  1631.         else if (tool==TOOL_TEMPLATE && e.getButton()==MouseEvent.BUTTON2) // flip the template, horizontal or vertical
  1632.         {
  1633.             boolean vertical=e.isShiftDown();
  1634.             for (int n=1;n<=template.numAtoms();n++)
  1635.                 template.setAtomPos(n,template.atomX(n)*(vertical?1:-1),template.atomY(n)*(vertical?-1:1));
  1636.             templDraw=template.clone();
  1637.             repaint();
  1638.         }      
  1639.  
  1640.         checkDirtiness();
  1641.     }
  1642.    
  1643.     public void mouseEntered(MouseEvent e)
  1644.     {
  1645.         boolean redraw=false;
  1646.         if (tool==TOOL_TEMPLATE && (trackX!=e.getX() || trackY!=e.getY())) redraw=true;
  1647.         trackX=e.getX(); trackY=e.getY();
  1648.         if (redraw) repaint();
  1649.     }
  1650.    
  1651.     public void mouseExited(MouseEvent e)
  1652.     {
  1653.         boolean redraw=false;
  1654.         if (tool==TOOL_TEMPLATE && (trackX!=e.getX() || trackY!=e.getY())) redraw=true;
  1655.         trackX=-1; trackY=-1;
  1656.         if (redraw) repaint();
  1657.     }
  1658.    
  1659.     public void mousePressed(MouseEvent e)
  1660.     {
  1661.         grabFocus();
  1662.  
  1663.         if ((tool==TOOL_CURSOR || (tool==TOOL_ROTATOR && !anySelected())) && e.getButton()==MouseEvent.BUTTON1 && editable)
  1664.         { // consider initiating a drag of the select, or translate position variety
  1665.             highlightAtom=highlightBond=0;
  1666.             boolean shift=(e.getModifiers()&MouseEvent.SHIFT_MASK)>0;
  1667.             boolean ctrl=(e.getModifiers()&MouseEvent.CTRL_MASK)>0;
  1668.             boolean alt=(e.getModifiers()&MouseEvent.ALT_MASK)>0;
  1669.             boolean anySelected=countSelected()>0;
  1670.             if (tool==TOOL_ROTATOR) {shift=false; ctrl=false; alt=false;} // can only select with rotator
  1671.             if (!ctrl && !alt)
  1672.             {
  1673.                 resetSelected(!shift);
  1674.                 int atom=pickAtom(e.getX(),e.getY());
  1675.                 if (atom>0) selected[atom-1]=true;
  1676.                 else toolDragReason=DRAG_SELECT;
  1677.             }
  1678.             else if (!shift && ctrl && !alt && anySelected) toolDragReason=DRAG_COPY;
  1679.             else if (!shift && !ctrl && alt && anySelected) toolDragReason=DRAG_MOVE;
  1680.             else if (shift && !ctrl && alt && anySelected) toolDragReason=DRAG_SCALE;
  1681.  
  1682.             toolDragX1=toolDragX2=e.getX();
  1683.             toolDragY1=toolDragY2=e.getY();
  1684.             repaint();
  1685.         }
  1686.         else if (tool==TOOL_ERASOR && e.getButton()==MouseEvent.BUTTON1) // initiate a drag-rect-delete sequence
  1687.         {
  1688.             if(MainApplet.USER_SELECTION){ deSelectAll(); } /* first remove marked stuff */
  1689.             highlightAtom=highlightBond=0;
  1690.             resetSelected(true);
  1691.             toolDragReason=DRAG_SELECT;
  1692.             toolDragX1=toolDragX2=e.getX();
  1693.             toolDragY1=toolDragY2=e.getY();
  1694.             repaint();
  1695.         }
  1696.         else if (tool==TOOL_ATOM) // note drag or change atom
  1697.         {
  1698.             toolBondFrom=pickAtom(e.getX(),e.getY());           // in case it gets...
  1699.             toolAtomSnap=e.getButton()==MouseEvent.BUTTON1;     // ... dragged later
  1700.         }
  1701.         else if (tool==TOOL_BOND && (e.getButton()==MouseEvent.BUTTON1 || e.getButton()==MouseEvent.BUTTON3)) // initiate bond drag
  1702.         {
  1703.             highlightAtom=highlightBond=0;
  1704.             toolBondDrag=false;
  1705.             toolBondFrom=pickAtom(e.getX(),e.getY());
  1706.             toolSnap=e.getButton()==MouseEvent.BUTTON1;
  1707.             if (toolBondFrom>0)
  1708.             {
  1709.                 toolBondToX=mol.atomX(toolBondFrom);
  1710.                 toolBondToY=mol.atomY(toolBondFrom);
  1711.                 repaint();
  1712.             }
  1713.             toolBondFromX=xToAng(e.getX());
  1714.             toolBondFromY=yToAng(e.getY());
  1715.             toolBondHit=pickBond(e.getX(),e.getY());
  1716.         }
  1717.         else if (tool==TOOL_TEMPLATE && e.getButton()==MouseEvent.BUTTON1) // slap a template right down
  1718.         {
  1719.             boolean swap=false;
  1720.             if (highlightAtom!=0 && templateIdx>0) adjustTemplateByAtom(highlightAtom);
  1721.             else if (highlightBond!=0 && templateIdx<0) swap=adjustTemplateByBond(highlightBond);
  1722.             else adjustTemplateByCoord(xToAng(trackX),yToAng(trackY));
  1723.                
  1724.             cacheUndo();
  1725.                
  1726.             if (templateIdx>=0)
  1727.                 templateSetByAtom(highlightAtom);
  1728.                 else
  1729.                 templateSetByBond(highlightBond,swap);
  1730.         }
  1731.         else if (tool==TOOL_ROTATOR && (e.getButton()==MouseEvent.BUTTON1 || e.getButton()==MouseEvent.BUTTON3) && anySelected())
  1732.         { // initiate a rotation-drag
  1733.             toolDragReason=DRAG_ROTATE;
  1734.             toolSnap=e.getButton()==MouseEvent.BUTTON1;
  1735.             if (highlightAtom>0) {toolDragX1=angToX(mol.atomX(highlightAtom)); toolDragY1=angToY(mol.atomY(highlightAtom));}
  1736.             else if (highlightBond>0)
  1737.             {
  1738.                 toolDragX1=angToX(0.5*(mol.atomX(mol.bondFrom(highlightBond))+mol.atomX(mol.bondTo(highlightBond))));
  1739.                 toolDragY1=angToY(0.5*(mol.atomY(mol.bondFrom(highlightBond))+mol.atomY(mol.bondTo(highlightBond))));
  1740.             }
  1741.             else {toolDragX1=e.getX(); toolDragY1=e.getY();}
  1742.             highlightAtom=highlightBond=0;
  1743.            
  1744.             toolDragX2=toolDragX1;
  1745.             toolDragY2=toolDragY1;
  1746.             repaint();
  1747.         }
  1748.         else if (tool==TOOL_CHARGE && highlightAtom>0) // offset charge
  1749.         {
  1750.             int chg=mol.atomCharge(highlightAtom);
  1751.             if (e.getButton()==MouseEvent.BUTTON1) chg+=toolCharge;
  1752.             else if (e.getButton()==MouseEvent.BUTTON3) chg-=toolCharge;
  1753.             else chg=0;
  1754.             cacheUndo();
  1755.             mol.setAtomCharge(highlightAtom,chg);
  1756.             repaint();
  1757.         }
  1758.        
  1759.         checkDirtiness();
  1760.     }
  1761.    
  1762.     public void mouseReleased(MouseEvent e)
  1763.     {
  1764.         if ((tool==TOOL_CURSOR && toolDragReason!=0) || (tool==TOOL_ROTATOR && toolDragReason==DRAG_SELECT) && editable)
  1765.         { // solidify a translate or select
  1766.             toolDragX2=e.getX();
  1767.             toolDragY2=e.getY();
  1768.             double mx=toolDragX2-toolDragX1,my=toolDragY2-toolDragY1;
  1769.            
  1770.             if (toolDragReason==DRAG_SELECT && dragged!=null)
  1771.             {
  1772.                 for (int n=0;n<mol.numAtoms();n++) selected[n]=selected[n] || dragged[n];
  1773.             }
  1774.             if (toolDragReason==DRAG_MOVE && selected!=null && mx*mx+my*my>5*5)
  1775.             {
  1776.                 double dx=mx/scale,dy=-my/scale;
  1777.                 cacheUndo();
  1778.                 for (int n=1;n<=mol.numAtoms();n++) if (selected[n-1])
  1779.                 {
  1780.                     mol.setAtomPos(n,mol.atomX(n)+dx,mol.atomY(n)+dy);
  1781.                 }
  1782.                 clearTemporary(false);
  1783.                 determineSize();
  1784.             }
  1785.             if (toolDragReason==DRAG_COPY && selected!=null && mx*mx+my*my>5*5)
  1786.             {
  1787.                 double dx=(toolDragX2-toolDragX1)/scale,dy=-(toolDragY2-toolDragY1)/scale;
  1788.                 int oldnumAtoms=mol.numAtoms(),oldnumBonds=mol.numBonds();
  1789.                 int[] newPos=new int[mol.numAtoms()];
  1790.                 cacheUndo();
  1791.                 for (int n=1;n<=oldnumAtoms;n++) if (selected[n-1])
  1792.                 {
  1793.                     newPos[n-1]=mol.addAtom(mol.atomElement(n),mol.atomX(n)+dx,mol.atomY(n)+dy,mol.atomCharge(n),mol.atomUnpaired(n));
  1794.                 }
  1795.                 for (int n=1;n<=oldnumBonds;n++) if (selected[mol.bondFrom(n)-1] && selected[mol.bondTo(n)-1])
  1796.                 {
  1797.                     mol.addBond(newPos[mol.bondFrom(n)-1],newPos[mol.bondTo(n)-1],mol.bondOrder(n),mol.bondType(n));
  1798.                 }
  1799.  
  1800.                 clearTemporary();
  1801.                 selected=new boolean[mol.numAtoms()];
  1802.                 for (int n=1;n<=mol.numAtoms();n++) selected[n-1]=n>oldnumAtoms;
  1803.                 determineSize();
  1804.             }
  1805.             if (toolDragReason==DRAG_SCALE && selected!=null && mx*mx+my*my>5*5)
  1806.             {
  1807.                 double extmul=dragExtendBy(mx,my);
  1808.                 double cx=0,cy=0;
  1809.                 int count=0;
  1810.                 for (int n=1;n<=mol.numAtoms();n++) if (selected[n-1]) {cx+=mol.atomX(n); cy+=mol.atomY(n); count++;}
  1811.                 cx/=count; cy/=count;
  1812.                 cacheUndo();
  1813.                 for (int n=1;n<=mol.numAtoms();n++) if (selected[n-1])
  1814.                 {
  1815.                     mol.setAtomPos(n,(mol.atomX(n)-cx)*extmul+cx,(mol.atomY(n)-cy)*extmul+cy);
  1816.                 }
  1817.                
  1818.                 clearTemporary(false);
  1819.                 determineSize();
  1820.             }
  1821.  
  1822.             toolDragReason=0;
  1823.             dragged=null;
  1824.             repaint();
  1825.         }
  1826.         if (tool==TOOL_ERASOR && toolDragReason!=0) // erase selection
  1827.         {
  1828.             toolDragX2=e.getX();
  1829.             toolDragY2=e.getY();
  1830.             if (toolDragReason==DRAG_SELECT && dragged!=null)
  1831.             {
  1832.                 for (int n=0;n<mol.numAtoms();n++) selected[n]=selected[n] || dragged[n];
  1833.                 deleteSelected();
  1834.                 clearTemporary();
  1835.             }
  1836.             toolDragReason=0;
  1837.             dragged=null;
  1838.             repaint();
  1839.         }
  1840.         else if (tool==TOOL_ROTATOR && toolDragReason==DRAG_ROTATE) // solidify a rotation
  1841.         {
  1842.             double dx=toolDragX2-toolDragX1,dy=toolDragY2-toolDragY1;
  1843.             double th=-Math.atan2(dy,dx)*180/Math.PI;
  1844.             if (toolSnap) th=Math.round(th/15)*15;
  1845.             if (Math.abs(th)>1)
  1846.             {
  1847.                 cacheUndo();
  1848.                 th=th*Math.PI/180;
  1849.                 double ax=xToAng(toolDragX1),ay=yToAng(toolDragY1);
  1850.                 for (int n=1;n<=mol.numAtoms();n++) if (selected[n-1])
  1851.                 {
  1852.                     double rx=mol.atomX(n)-ax,ry=mol.atomY(n)-ay;
  1853.                     double rth=Math.atan2(ry,rx),ext=Math.sqrt(rx*rx+ry*ry);
  1854.                     mol.setAtomPos(n,ax+ext*Math.cos(rth+th),ay+ext*Math.sin(rth+th));
  1855.                 }
  1856.                 clearTemporary(false);
  1857.                 determineSize();
  1858.             }
  1859.  
  1860.             toolDragReason=0;
  1861.             dragged=null;
  1862.             repaint();
  1863.         }
  1864.         else if (tool==TOOL_ATOM && toolAtomDrag && toolBondFrom>0) // place a new atom-from
  1865.         {
  1866.             cacheUndo();
  1867.             mol.addAtom(toolAtomType,toolBondToX,toolBondToY);
  1868.             mol.addBond(toolBondFrom,mol.numAtoms(),1);
  1869.             clearTemporary();
  1870.             determineSize();
  1871.             toolAtomDrag=false;
  1872.             toolBondFrom=0;
  1873.             repaint();
  1874.         }
  1875.         else if (tool==TOOL_BOND) // bond addition, possibly by adding new atoms, too
  1876.         {
  1877.             toolBondToX=xToAng(e.getX());
  1878.             toolBondToY=yToAng(e.getY());
  1879.  
  1880.             int joinTo=pickAtom(e.getX(),e.getY());
  1881.             if (toolBondFrom>0 && joinTo==0 && toolSnap)
  1882.             {
  1883.                 snapToolBond();
  1884.                 joinTo=pickAtom((int)angToX(toolBondToX),(int)angToY(toolBondToY));
  1885.             }
  1886.            
  1887.             if (e.getButton()==MouseEvent.BUTTON1 && toolBondFrom==0 && toolBondHit>0) // change hit bond order
  1888.             {
  1889.                 int i=pickBond(e.getX(),e.getY());
  1890.                 if (i==toolBondHit)
  1891.                 {
  1892.                     cacheUndo();
  1893.                     if (toolBondOrder==mol.bondOrder(i) && toolBondType==mol.bondType(i))
  1894.                         mol.setBondFromTo(i,mol.bondTo(i),mol.bondFrom(i));
  1895.                     mol.setBondOrder(i,toolBondOrder);
  1896.                     mol.setBondType(i,toolBondType);
  1897.                     clearTemporary();
  1898.                 }
  1899.             }
  1900.             else if (toolBondFrom==0) // create a new bond from/in the middle of nowhere, possibly connected to something
  1901.             {
  1902.                 int a1=0,a2=0;
  1903.                 double x1=0,x2=0,y1=0,y2=0;
  1904.                 if (toolBondDrag)
  1905.                 {
  1906.                     if (toolSnap) snapToolBond();
  1907.                     x1=toolBondFromX;
  1908.                     y1=toolBondFromY;
  1909.                     a2=pickAtom(e.getX(),e.getY());
  1910.                     if (a2>0) {x2=mol.atomX(a2); y2=mol.atomY(a2);} else {x2=toolBondToX; y2=toolBondToY;}
  1911.                 }
  1912.                 else
  1913.                 {
  1914.                     x1=x2=xToAng(e.getX()); if ((e.getModifiers()&MouseEvent.SHIFT_MASK)>0) {x1-=0.5*IDEALBOND; x2+=0.5*IDEALBOND;}
  1915.                     y1=y2=yToAng(e.getY()); if ((e.getModifiers()&MouseEvent.SHIFT_MASK)==0) {y1-=0.5*IDEALBOND; y2+=0.5*IDEALBOND;}
  1916.                 }
  1917.                 double dx=x2-x1,dy=y2-y1;
  1918.                 if (dx*dx+dy*dy>0.5*0.5)
  1919.                 {
  1920.                     cacheUndo();
  1921.                     a1=mol.addAtom("C",x1,y1,0,0);
  1922.                     if (a2==0) a2=mol.addAtom("C",x2,y2,0,0);
  1923.                     mol.addBond(a1,a2,toolBondOrder);
  1924.                     clearTemporary();
  1925.                 }
  1926.                 repaint();
  1927.             }
  1928.             else if (joinTo>0 && joinTo!=toolBondFrom) // link two atoms together
  1929.             {
  1930.                 cacheUndo();
  1931.                 mol.addBond(toolBondFrom,joinTo,toolBondOrder);
  1932.                 mol.setBondType(mol.numBonds(),toolBondType);
  1933.                 clearTemporary();
  1934.             }
  1935.             else if (toolBondFrom>0) // draw a new bond out to some place not specified by the user, i.e. a healthy guess
  1936.             {
  1937.                 double dx=toolBondToX-mol.atomX(toolBondFrom),dy=toolBondToY-mol.atomY(toolBondFrom);
  1938.                 if (toolBondFrom==joinTo)
  1939.                 {
  1940.                     int adj[]=mol.atomAdjList(toolBondFrom);
  1941.                     ArrayList<Double> poss=new ArrayList<Double>();
  1942.                     double ax=mol.atomX(toolBondFrom),ay=mol.atomY(toolBondFrom);
  1943.                     if (adj.length==0) poss.add(0.0);
  1944.                     else if (adj.length==1)
  1945.                     {
  1946.                         double ang=Math.atan2(mol.atomY(adj[0])-ay,mol.atomX(adj[0])-ax)*180/Math.PI;
  1947.                         if (toolBondOrder!=3)
  1948.                         {
  1949.                             poss.add(ang+120);
  1950.                             poss.add(ang-120);
  1951.                         }
  1952.                         else poss.add(ang+180);
  1953.                     }
  1954.                     else if (adj.length==2)
  1955.                     {
  1956.                         double ang1=Math.atan2(mol.atomY(adj[0])-ay,mol.atomX(adj[0])-ax)*180/Math.PI;
  1957.                         double ang2=Math.atan2(mol.atomY(adj[1])-ay,mol.atomX(adj[1])-ax)*180/Math.PI;
  1958.                         if (ang2<ang1) ang2+=360;
  1959.                         if (ang2-ang1<180) poss.add(0.5*(ang1+ang2)+180); else poss.add(0.5*(ang1+ang2));
  1960.                     }
  1961.                     else for (int n=0;n<adj.length;n++)
  1962.                     {
  1963.                         double ang=Math.atan2(mol.atomY(adj[n])-ay,mol.atomX(adj[n])-ax)*180/Math.PI;
  1964.                         poss.add(ang+180);
  1965.                     }
  1966.                     double ang=poss.get(0);
  1967.                     if (poss.size()>1)
  1968.                     {
  1969.                         int best=-1;
  1970.                         double bestScore=0;
  1971.                         for (int n=0;n<poss.size();n++)
  1972.                         {
  1973.                             double nx=ax+IDEALBOND*Math.cos(poss.get(n)*Math.PI/180);
  1974.                             double ny=ay+IDEALBOND*Math.sin(poss.get(n)*Math.PI/180);
  1975.                             double score=0;
  1976.                             for (int i=1;i<=mol.numAtoms();i++)
  1977.                             {
  1978.                                 dx=mol.atomX(i)-nx;
  1979.                                 dy=mol.atomY(i)-ny;
  1980.                                 score+=1/Math.min(1000,dx*dx+dy*dy);
  1981.                             }
  1982.                             if (best<0 || score<bestScore) {best=n; bestScore=score;}
  1983.                         }
  1984.                         ang=poss.get(best);
  1985.                     }
  1986.                    
  1987.                     dx=IDEALBOND*Math.cos(ang*Math.PI/180);
  1988.                     dy=IDEALBOND*Math.sin(ang*Math.PI/180);
  1989.                     toolBondToX=ax+dx;
  1990.                     toolBondToY=ay+dy;
  1991.                 }
  1992.                 if (dx*dx+dy*dy>0.5)
  1993.                 {
  1994.                     cacheUndo();
  1995.                     mol.addAtom("C",toolBondToX,toolBondToY);
  1996.                     mol.addBond(toolBondFrom,mol.numAtoms(),toolBondOrder);
  1997.                     mol.setBondType(mol.numBonds(),toolBondType);
  1998.                     clearTemporary();
  1999.                     determineSize();
  2000.                 }
  2001.             }
  2002.  
  2003.             toolBondDrag=false;
  2004.             toolBondFrom=0;
  2005.             toolBondHit=0;
  2006.             repaint();
  2007.         }
  2008.  
  2009.         checkDirtiness();
  2010.     }
  2011.    
  2012.     public void mouseMoved(MouseEvent e)
  2013.     {
  2014.         boolean redraw=false;
  2015.        
  2016.         if (trackX!=e.getX() || trackY!=e.getY())
  2017.         {
  2018.             if (tool==TOOL_TEMPLATE) redraw=true;
  2019.         }
  2020.        
  2021.         trackX=e.getX();
  2022.         trackY=e.getY();
  2023.  
  2024.         if (e.getButton()==0)
  2025.         {
  2026.             int mx=e.getX(),my=e.getY();
  2027.             int newAtom=0,newBond=0;
  2028.  
  2029.             newAtom=pickAtom(mx,my);
  2030.             if (newAtom==0) newBond=pickBond(mx,my);
  2031.            
  2032.             if (tool==TOOL_TEMPLATE && templateIdx>0) newBond=0;
  2033.             if (tool==TOOL_TEMPLATE && templateIdx<0) newAtom=0;
  2034.            
  2035.             if (newAtom!=highlightAtom || newBond!=highlightBond)
  2036.             {
  2037.                 highlightAtom=newAtom;
  2038.                 highlightBond=newBond;
  2039.                 redraw=true;
  2040.             }
  2041.         }
  2042.        
  2043.         if (redraw) repaint();
  2044.     }
  2045.    
  2046.     public void mouseDragged(MouseEvent e)
  2047.     {
  2048.         boolean redraw=false;
  2049.         if (tool==TOOL_TEMPLATE && (trackX!=e.getX() || trackY!=e.getY())) redraw=true;
  2050.         trackX=e.getX(); trackY=e.getY();
  2051.    
  2052.         if ((tool==TOOL_CURSOR && toolDragReason!=0) || (tool==TOOL_ERASOR && toolDragReason!=0) ||
  2053.             (tool==TOOL_ROTATOR && toolDragReason==DRAG_SELECT))
  2054.         {
  2055.             toolDragX2=e.getX();
  2056.             toolDragY2=e.getY();
  2057.             if (toolDragReason==DRAG_SELECT)
  2058.             {
  2059.                 int x=(int)toolDragX1,y=(int)toolDragY1,w=(int)toolDragX2-x,h=(int)toolDragY2-y;
  2060.                 if (w<0) {w=-w; x-=w;}
  2061.                 if (h<0) {h=-h; y-=h;}
  2062.                 dragged=new boolean[mol.numAtoms()];
  2063.                 for (int n=0;n<mol.numAtoms();n++) dragged[n]=px[n]>=x && px[n]<=x+w && py[n]>=y && py[n]<=y+h;
  2064.             }
  2065.             redraw=true;
  2066.         }
  2067.         else if (tool==TOOL_ROTATOR && toolDragReason==DRAG_ROTATE)
  2068.         {
  2069.             toolDragX2=e.getX();
  2070.             toolDragY2=e.getY();
  2071.             redraw=true;
  2072.         }
  2073.         else if (tool==TOOL_ATOM && toolBondFrom!=0)
  2074.         {
  2075.             if (!toolAtomDrag)
  2076.             {
  2077.                 double dx=xToAng(e.getX())-mol.atomX(toolBondFrom),dy=yToAng(e.getY())-mol.atomY(toolBondFrom);
  2078.                 if (dx*dx+dy*dy>0.8*0.8)
  2079.                 {
  2080.                     toolAtomDrag=true;
  2081.                     toolBondOrder=1;
  2082.                     toolBondType=Molecule.BONDTYPE_NORMAL;
  2083.                 }
  2084.             }
  2085.             if (toolAtomDrag)
  2086.             {
  2087.                 toolBondToX=xToAng(e.getX());
  2088.                 toolBondToY=yToAng(e.getY());
  2089.                 if (toolAtomSnap) snapToolBond();
  2090.                 redraw=true;
  2091.             }
  2092.         }
  2093.         else if (tool==TOOL_BOND /*&& toolBondFrom!=0*/)
  2094.         {
  2095.             toolBondToX=xToAng(e.getX());
  2096.             toolBondToY=yToAng(e.getY());
  2097.             int joinTo=pickAtom(e.getX(),e.getY());
  2098.             if (!toolBondDrag)
  2099.                 if (Math.abs(toolBondToX-toolBondFromX)>2/scale || Math.abs(toolBondToY-toolBondFromY)>2/scale) toolBondDrag=true;
  2100.             if (joinTo>0) {toolBondToX=mol.atomX(joinTo); toolBondToY=mol.atomY(joinTo);}
  2101.             else if (toolSnap) snapToolBond();
  2102.             redraw=true;
  2103.         }
  2104.  
  2105.         if (redraw) repaint();
  2106.         checkDirtiness();
  2107.     }
  2108.    
  2109.     public void mouseWheelMoved(MouseWheelEvent e)
  2110.     {
  2111.         if (tool==TOOL_TEMPLATE)
  2112.         {
  2113.             double cx=0,cy=0;
  2114.             for (int n=1;n<=template.numAtoms();n++) {cx+=template.atomX(n); cy+=template.atomY(n);}
  2115.             cx/=template.numAtoms();
  2116.             cy/=template.numAtoms();
  2117.        
  2118.             double accel=e.isShiftDown() ? 3 : 1;
  2119.        
  2120.             if (e.isControlDown()) // scale
  2121.             {
  2122.                 double factor=1-0.1*accel*e.getWheelRotation();
  2123.                 for (int n=1;n<=template.numAtoms();n++)
  2124.                     template.setAtomPos(n,cx+(template.atomX(n)-cx)*factor,cy+(template.atomY(n)-cy)*factor);
  2125.             }
  2126.             else // rotate
  2127.             {
  2128.                 double radians=5*accel*Math.PI/180*e.getWheelRotation();
  2129.                 for (int n=1;n<=template.numAtoms();n++)
  2130.                 {
  2131.                     double dx=template.atomX(n)-cx,dy=template.atomY(n)-cy;
  2132.                     double dist=Math.sqrt(dx*dx+dy*dy),theta=Math.atan2(dy,dx);
  2133.                     template.setAtomPos(n,cx+dist*Math.cos(theta+radians),cy+dist*Math.sin(theta+radians));
  2134.                 }
  2135.             }
  2136.             templDraw=template.clone();
  2137.             repaint();
  2138.         }
  2139.     }
  2140.    
  2141.     // Other callbacks...
  2142.    
  2143.     public void focusGained(FocusEvent e) {}
  2144.     public void focusLost(FocusEvent e)
  2145.     {
  2146.         if (e.getSource()==toolAtomEditBox) completeAtomEdit();
  2147.     }
  2148.     public void keyPressed(KeyEvent e) {}
  2149.     public void keyReleased(KeyEvent e) {}
  2150.     public void keyTyped(KeyEvent e)
  2151.     {
  2152.         if (e.getSource()==toolAtomEditBox)
  2153.         {
  2154.             if (e.getKeyChar()=='\n') completeAtomEdit();
  2155.         }
  2156.     }
  2157.  
  2158.     public void componentHidden(ComponentEvent e) {}
  2159.     public void componentMoved(ComponentEvent e) {}
  2160.     public void componentResized(ComponentEvent e)
  2161.     {
  2162.         if (autoScale) {scaleToFit(); repaint();}
  2163.     }
  2164.     public void componentShown(ComponentEvent e)
  2165.     {
  2166.         if (autoScale) {scaleToFit(); repaint();}
  2167.     }
  2168.    
  2169.     // Class for deciding when molecule data can be dragged into this control.
  2170.    
  2171.     class TransferMoleculeDest extends TransferHandler
  2172.     {
  2173.         EditorPane dest;
  2174.  
  2175.         public TransferMoleculeDest(EditorPane dest) {this.dest=dest;}
  2176.         public boolean canImport(TransferHandler.TransferSupport info)
  2177.         {
  2178.             if (!info.isDataFlavorSupported(DataFlavor.stringFlavor)) return false;
  2179.             try
  2180.             {
  2181.                 String data=(String)info.getTransferable().getTransferData(DataFlavor.stringFlavor);
  2182.                 Molecule mol=MoleculeStream.readUnknown(new BufferedReader(new StringReader(data)));
  2183.                 return mol!=null;
  2184.             }
  2185.             catch (InvalidDnDOperationException e)
  2186.             {
  2187.                 // this is thrown when dragging between different processes; it means we can't actually check the
  2188.                 // data here, which is suboptimal, but not the end of the world
  2189.                 return true;
  2190.             }
  2191.             catch (UnsupportedFlavorException e) {return false;}
  2192.             catch (IOException e) {return false;}
  2193.         }
  2194.         public boolean importData(TransferHandler.TransferSupport info)
  2195.         {
  2196.             if (!info.isDataFlavorSupported(DataFlavor.stringFlavor)) return false;
  2197.             try
  2198.             {
  2199.                 String data=(String)info.getTransferable().getTransferData(DataFlavor.stringFlavor);
  2200.                 Molecule mol=MoleculeStream.readUnknown(new BufferedReader(new StringReader(data)));
  2201.                 if (mol==null || mol.numAtoms()==0) return false;
  2202.                 Point pos=info.getDropLocation().getDropPoint();
  2203.                 dest.addFragmentPosition(mol,(int)pos.getX(),(int)pos.getY());
  2204.                 return true;
  2205.             }
  2206.             catch (UnsupportedFlavorException e) {return false;}
  2207.             catch (IOException e) {return false;}
  2208.         }
  2209.     }
  2210.    
  2211. // jm.evers 13/1/2010 rotating according to applet params ; called by MainPanel.java
  2212.     public void RotateMolecule(){
  2213.      int degrees = MainApplet.rotation;
  2214.       if(degrees != 0){
  2215.         double dist;
  2216.         double dx;
  2217.         double dy;
  2218.         double theta;
  2219.         double radians=(double)(degrees*Math.PI/180);
  2220.         for(int n=1;n<=mol.numAtoms();n++){
  2221.          dx=mol.atomX(n);
  2222.          dy=mol.atomY(n);
  2223.          dist=Math.sqrt(dx*dx+dy*dy);
  2224.          theta=Math.atan2(dy,dx);
  2225.          try{
  2226.           mol.setAtomPos(n,dist*Math.cos(theta+radians),dist*Math.sin(theta+radians));
  2227.          }catch(Exception e){System.out.println("problem with rotation : "+e.toString());}
  2228.         }
  2229.         repaint();
  2230.        }
  2231.      }
  2232.                        
  2233.     public static boolean[] GrowArray(boolean array[], int newlength){
  2234.         boolean[] grow;
  2235.         int oldlength = array.length;
  2236.         grow = new boolean[ newlength ];
  2237.         for(int i=0; i < oldlength; i++){
  2238.             grow[i] = array[i];
  2239.         }
  2240.         return grow;
  2241.     }
  2242. }
  2243.  
  2244.  
  2245.  
  2246.  
  2247.  
  2248.