Back to index

wims  3.65+svn20090927
EditorPane.java
Go to the documentation of this file.
00001 /*
00002     WIMSchem Elements: Chemistry molecular diagram drawing tool.
00003     
00004     (c) 2005 Dr. Alex M. Clark
00005     
00006     Released as GNUware, under the Gnu Public License (GPL)
00007     
00008     See www.gnu.org for details.
00009 */
00010 
00011 package WIMSchem;
00012 
00013 import java.util.*;
00014 import java.text.*;
00015 import java.awt.*;
00016 import java.awt.geom.*;
00017 import java.awt.event.*;
00018 import javax.swing.*;
00019 import javax.swing.event.*;
00020 
00021 /*
00022     Custom widget for viewing and editing a molecular structure. The widget is a rather heavy one, and features a lot of the work-horse
00023     functions for various tools, including the specific details of user interaction, and drawing of the structure itself, with various
00024     annotations for editing and selection. Can optionally be used as a lightweight widget with just view and selection.
00025 */
00026 
00027 public class EditorPane extends JComponent 
00028     implements MouseListener, MouseMotionListener, FocusListener, KeyListener, MouseWheelListener, ComponentListener
00029 {
00030     Molecule mol;
00031     boolean editable=true,hasBorder=false,autoScale=false;
00032     static final double IDEALBOND=1.5; // stylised bond distance (Angstroms)
00033     // note: px=(atomX+offsetX)*scale; ax=px/scale-offsetX; offsetX=px/scale-ax (and same for Y)
00034     double offsetX=0,offsetY=0; // in Angstrom units
00035     double scale=DrawMolecule.DEFSCALE; // pixels per Angstrom
00036     boolean[] selected=null,dragged=null;
00037     double[] px=null,py=null,rw=null,rh=null;
00038     double[][] bfx=null,bfy=null,btx=null,bty=null;
00039     int highlightAtom=0,highlightBond=0;
00040     int showMode=DrawMolecule.SHOW_ALL_ELEMENTS;
00041     // jm.evers 27/12/2008
00042     boolean showHydr=MainPanel.viewH;                                                                                                                           
00043     //
00044     boolean showSter=false;
00045 
00046     static final int TOOL_CURSOR=1;
00047     static final int TOOL_ROTATOR=2;
00048     static final int TOOL_ERASOR=3;
00049     static final int TOOL_ATOM=4;
00050     static final int TOOL_BOND=5;
00051     static final int TOOL_CHARGE=6;
00052     static final int TOOL_TEMPLATE=7;
00053     
00054     static final int DRAG_SELECT=1;
00055     static final int DRAG_MOVE=2;
00056     static final int DRAG_COPY=3;
00057     static final int DRAG_SCALE=4;
00058     static final int DRAG_ROTATE=5;
00059     
00060     int trackX=-1,trackY=-1; // last seen position of mouse
00061     
00062     boolean isSelectionPane=false; // false=is for editing; true=is for viewing and selecting only
00063     int selBoxW=0,selBoxH=0; // size to this, for selection panes
00064     
00065     MolSelectListener selectListen=null;
00066         
00067     int tool=0;
00068     int toolDragReason=0;
00069     double toolDragX1,toolDragY1,toolDragX2,toolDragY2;
00070     String toolAtomType=null;
00071     boolean toolAtomDrag,toolAtomSnap;
00072     int toolAtomEditSel=0,toolAtomEditX,toolAtomEditY;
00073     JTextField toolAtomEditBox=null;
00074 
00075     int toolBondOrder=0,toolBondType=0,toolBondFrom=0,toolBondHit=0;
00076     double toolBondFromX=0,toolBondFromY=0,toolBondToX=0,toolBondToY=0;
00077     boolean toolSnap,toolBondDrag=false;
00078     
00079     int toolCharge=0;
00080     
00081     static final int UNDO_LEVELS=10;
00082     class EditState{
00083        Molecule Molecule;
00084        boolean Selected[];
00085     };
00086     EditState[] undo=null,redo=null;
00087     
00088     Molecule template=null,templDraw=null;
00089     int templateIdx=0;
00090     
00091     Molecule lastCleanMol=null;
00092     boolean lastDirty=false;
00093 
00094     // ------------------ public functions --------------------
00095 
00096     // Constructor for fully-fledged interactive editing panes.
00097     public EditorPane() {Init();}
00098 
00099     // Constructor for "selection only" editor panes.
00100     public EditorPane(int width,int height) 
00101     {
00102        isSelectionPane=true; 
00103        selBoxW=width;
00104        selBoxH=height;
00105        Init();
00106     }
00107     
00108     void Init()
00109     {
00110        mol=new Molecule();
00111        DetermineSize();
00112        addMouseListener(this);
00113        addMouseMotionListener(this);
00114        addMouseWheelListener(this);
00115        addComponentListener(this);
00116     }
00117     
00118     // obtain underlying molecule; not a copy, careful about modifying
00119     public Molecule MolData() {return mol;}
00120     
00121     public boolean IsEmpty() {return mol.NumAtoms()==0;}
00122     
00123     // unit operation equivalent to deleting all atoms
00124     public void Clear()
00125     {
00126        CacheUndo();
00127        
00128        mol=new Molecule();
00129        ClearTemporary();
00130        DetermineSize();
00131        repaint();
00132        
00133        CheckDirtiness();
00134     }
00135 
00136     // override the underlying molecule
00137     public void Replace(Molecule Mol) {Replace(Mol,false,true);}
00138     public void Replace(Molecule Mol,boolean ClearSelection) {Replace(Mol,ClearSelection,true);}
00139     public void Replace(Molecule Mol,boolean ClearSelection,boolean Repaint) 
00140     {
00141        if (mol.NumAtoms()!=Mol.NumAtoms()) ClearSelection=true;
00142        mol=Mol;
00143        ClearTemporary(ClearSelection);
00144        if (Repaint) repaint();
00145     }
00146     
00147     // set which object, if any, gets a response when an atom is "selected" with a mouse click
00148     public void SetMolSelectListener(MolSelectListener listen) {selectListen=listen;}
00149     
00150     // by default the editor pane captures lots of events and allows much editor; this function can be used to turn it off
00151     public void SetEditable(boolean Editable) {editable=Editable;}
00152     
00153     // if true, will draw a border around the edge
00154     public void SetBorder(boolean HasBorder) {hasBorder=HasBorder;}
00155 
00156     // if true, every time the size changes, the molecule will scale-to-fit    
00157     public void SetAutoScale(boolean AutoScale) {autoScale=AutoScale;}
00158 
00159     // informs the editor that the current state has been synchronised with what is in a disk file, or something equivalent
00160     public void NotifySaved(){
00161        lastCleanMol=mol.Clone();
00162        lastDirty=false;
00163        if (selectListen!=null) selectListen.DirtyChanged(false);
00164     }
00165     
00166     // dirty==true when there have been some changes since the last modification
00167     public boolean IsDirty() {return lastDirty;}
00168     
00169     // checks to see whether the current molecule is the same as the last saved state; notifies if different; note that this is done by
00170     // an actual molecule comparison, which makes tracking changes a lot simpler, and also a {do something/restore it somehow} sequence
00171     // is functionally equivalent to undo, which is nice
00172     void CheckDirtiness(){
00173        boolean nowDirty=mol.CompareTo(lastCleanMol)!=0;
00174        
00175        if (nowDirty!=lastDirty)
00176        {
00177            if (selectListen!=null) selectListen.DirtyChanged(nowDirty);
00178            lastDirty=nowDirty;
00179        }
00180     }
00181         
00182     // affect the way the molecule is rendered
00183     public void SetShowMode(int ShowMode){
00184        if (showMode==ShowMode) return;
00185        showMode=ShowMode;
00186        repaint();
00187     }
00188     public void SetShowHydrogens(boolean ShowHydr)
00189     {
00190        if (showHydr==ShowHydr) return;
00191        showHydr=ShowHydr;
00192        repaint();
00193     }
00194     public void SetShowStereolabels(boolean ShowSter)
00195     {
00196        if (showSter==ShowSter) return;
00197        showSter=ShowSter;
00198        repaint();
00199     }
00200     
00201     // notify selection of various tools
00202     public void SetToolCursor() 
00203     {
00204        tool=TOOL_CURSOR; 
00205        repaint();
00206     }
00207     public void SetToolRotator()
00208     {
00209        tool=TOOL_ROTATOR;
00210        repaint();
00211     }
00212     public void SetToolErasor() 
00213     {
00214        tool=TOOL_ERASOR; 
00215        repaint();
00216     }
00217     public void SetToolAtom(String Atom) 
00218     {
00219        tool=TOOL_ATOM;
00220        toolAtomType=Atom;
00221        toolAtomDrag=false;
00222        toolAtomSnap=false;
00223        toolBondFrom=0;
00224        toolBondToX=0;
00225        toolBondToY=0;
00226        repaint();
00227     }
00228     public void SetToolBond(int Order)
00229     {
00230        tool=TOOL_BOND;
00231        toolBondFrom=0;
00232        if (Order>=0) {toolBondOrder=Order; toolBondType=Molecule.BONDTYPE_NORMAL;}
00233        else 
00234        {
00235            toolBondOrder=1;
00236            if (Order==-1) toolBondType=Molecule.BONDTYPE_INCLINED;
00237            else if (Order==-2) toolBondType=Molecule.BONDTYPE_DECLINED;
00238            else if (Order==-3) toolBondType=Molecule.BONDTYPE_UNKNOWN;
00239        }
00240        repaint();
00241     }
00242     public void SetToolCharge(int DChg)
00243     {
00244        tool=TOOL_CHARGE;
00245        toolCharge=DChg;
00246     }
00247     public void SetToolTemplate(Molecule Templ,int Idx)
00248     {
00249        tool=TOOL_TEMPLATE;
00250        template=Templ;
00251        templateIdx=Idx;
00252        repaint();
00253     }
00254     
00255     // whether or not there is anything in the undo/redo stacks
00256     public boolean CanUndo() {return undo!=null && undo[0]!=null;}
00257     public boolean CanRedo() {return redo!=null && redo[0]!=null;}
00258     
00259     // cause the actual undo/redo to happen
00260     public void Undo()
00261     {
00262        if (!CanUndo()) return;
00263        
00264        if (redo==null) redo=new EditState[UNDO_LEVELS];
00265        for (int n=UNDO_LEVELS-1;n>0;n--) redo[n]=redo[n-1];
00266        redo[0]=new EditState();
00267        redo[0].Molecule=mol;
00268        redo[0].Selected=selected;
00269        
00270        mol=undo[0].Molecule;
00271        selected=undo[0].Selected;
00272        for (int n=0;n<UNDO_LEVELS-1;n++) undo[n]=undo[n+1];
00273        ClearTemporary(false);
00274        DetermineSize();
00275        repaint();
00276        
00277        CheckDirtiness();
00278     }
00279     public void Redo()
00280     {
00281        if (!CanRedo()) return;
00282        
00283        if (undo==null) undo=new EditState[UNDO_LEVELS];
00284        for (int n=UNDO_LEVELS-1;n>0;n--) undo[n]=undo[n-1];
00285        undo[0]=new EditState();
00286        undo[0].Molecule=mol;
00287        undo[0].Selected=selected;
00288        
00289        mol=redo[0].Molecule;
00290        selected=redo[0].Selected;
00291        for (int n=0;n<UNDO_LEVELS-1;n++) redo[n]=redo[n+1];
00292        
00293        ClearTemporary(false);
00294        DetermineSize();
00295        repaint();
00296        
00297        CheckDirtiness();
00298     }
00299     
00300     // fits the molecule on the screen and centres everything; very pleasant thing to have at certain junctures, but not too often
00301     public void ScaleToFit() {ScaleToFit(20);}
00302     public void ScaleToFit(double MaxScale)
00303     {
00304        ClearTemporary();
00305 
00306        double mw=2+mol.RangeX(),mh=2+mol.RangeY();
00307        Rectangle vis=getVisibleRect();
00308        if (vis.getWidth()==0 || vis.getHeight()==0) vis=new Rectangle(0,0,getWidth(),getHeight());
00309        double sw=selBoxW>vis.getWidth() ? selBoxW : vis.getWidth();
00310        double sh=selBoxH>vis.getHeight() ? selBoxH : vis.getHeight();
00311        scale=Math.min(Math.min(sw/mw,sh/mh),MaxScale);
00312        offsetX=0.5*(sw/scale-mol.RangeX())-mol.MinX();
00313        offsetY=0.5*(sh/scale-mol.RangeY())+mol.MaxY();
00314     }
00315 
00316     // change the magnification, and adjust scrollbars etc accordingly
00317     public void ZoomFull() 
00318     {
00319        ScaleToFit();
00320        DetermineSize();
00321        repaint();
00322     }
00323     public void ZoomIn(double Mag)
00324     {
00325        scale*=Mag;
00326        DetermineSize();
00327        repaint();
00328     }
00329     public void ZoomOut(double Mag)
00330     {
00331        scale/=Mag;
00332        DetermineSize();
00333        repaint();
00334     }
00335     
00336     // select all atoms
00337     public void SelectAll()
00338     {
00339        selected=new boolean[mol.NumAtoms()];
00340        for (int n=0;n<mol.NumAtoms();n++) selected[n]=true;
00341        repaint();
00342     }
00343     
00344     // finds a nice place to put the new fragment which does not overlap existing content, then appends the atoms & bonds; note that
00345     // ownership of Frag is assumed
00346     public void AddArbitraryFragment(Molecule Frag)
00347     {
00348        if (Frag.NumAtoms()==0) return;
00349     
00350        CacheUndo();
00351        if (mol.NumAtoms()==0) 
00352        {
00353            mol=Frag;
00354            ClearTemporary();
00355            ScaleToFit();
00356            DetermineSize();
00357            repaint();
00358            CheckDirtiness();
00359            return;
00360        }
00361     
00362        final double dirX[]={1,0,-1,1,-1,1,0,-1},dirY[]={1,1,1,0,0,-1,-1,-1};
00363        double dx[]=new double[8],dy[]=new double[8],score[]=new double[8];
00364        
00365        for (int n=0;n<8;n++)
00366        {
00367            double vx=dirX[n],vy=dirY[n];
00368 
00369            if (n==0 || n==3 || n==5) {dx[n]=mol.MinX()-Frag.MaxX();}
00370            else if (n==2 || n==4 || n==7) {dx[n]=mol.MaxX()-Frag.MinX();}
00371            else dx[n]=0.5*(mol.MinX()+mol.MaxX()-Frag.MinX()-Frag.MaxX());
00372            
00373            if (n==5 || n==6 || n==7) {dy[n]=mol.MinY()-Frag.MaxY();}
00374            else if (n==0 || n==1 || n==2) {dy[n]=mol.MaxY()-Frag.MinY();}
00375            else dy[n]=0.5*(mol.MinY()+mol.MaxY()-Frag.MinY()-Frag.MaxY());
00376            
00377            dx[n]-=vx;
00378            dy[n]-=vy;
00379            score[n]=FragPosScore(Frag,dx[n],dy[n]);
00380            
00381            vx*=0.25;
00382            vy*=0.25;
00383            for (int iter=100;iter>0;iter--)
00384            {
00385               double iscore=FragPosScore(Frag,dx[n]+vx,dy[n]+vy);
00386               if (iscore<=score[n]) break;
00387               score[n]=iscore;
00388               dx[n]+=vx;
00389               dy[n]+=vy;
00390            }
00391            for (int iter=100;iter>0;iter--) for (int d=0;d<8;d++)
00392            {
00393               vx=dirX[d]*0.1;
00394               vy=dirY[d]*0.1;
00395               double iscore=FragPosScore(Frag,dx[n]+vx,dy[n]+vy);
00396               if (iscore<=score[n]) break;
00397               score[n]=iscore;
00398               dx[n]+=vx;
00399               dy[n]+=vy;
00400            }
00401        }
00402        
00403        int best=0;
00404        for (int n=1;n<8;n++) if (score[n]>score[best]) best=n;
00405        
00406        for (int n=1;n<=Frag.NumAtoms();n++) Frag.SetAtomPos(n,Frag.AtomX(n)+dx[best],Frag.AtomY(n)+dy[best]);
00407        int base=mol.NumAtoms();
00408        mol.Append(Frag);
00409        
00410        ClearTemporary();
00411        ScaleToFit();
00412        DetermineSize();
00413        
00414        selected=new boolean[mol.NumAtoms()];
00415        for (int n=0;n<mol.NumAtoms();n++) selected[n]=n>=base;
00416        
00417        repaint();
00418        CheckDirtiness();
00419     }
00420     
00421     // scoring function for above: more congested is better, but any two atoms < 1A = zero; post-biased to favour square aspect ratio
00422     double FragPosScore(Molecule Frag,double DX,double DY)
00423     {
00424        double score=0;
00425        for (int i=1;i<=mol.NumAtoms();i++)
00426        for (int j=1;j<=Frag.NumAtoms();j++)
00427        {
00428            double dx=Frag.AtomX(j)+DX-mol.AtomX(i),dy=Frag.AtomY(j)+DY-mol.AtomY(i);
00429            double dist2=dx*dx+dy*dy;
00430            if (dist2<1) return 0;
00431            score+=1/dist2;
00432        }
00433        double minX=Math.min(Frag.MinX()+DX,mol.MinX()),maxX=Math.max(Frag.MaxX()+DX,mol.MaxX());
00434        double minY=Math.min(Frag.MinY()+DY,mol.MinY()),maxY=Math.max(Frag.MaxY()+DY,mol.MaxY());
00435        double rangeX=Math.max(1,maxX-minX),rangeY=Math.max(1,maxY-minY);
00436        double ratio=Math.max(rangeX/rangeY,rangeY/rangeX);
00437        return score/ratio;
00438     }
00439     
00440     // returns the # of atoms in selection set
00441     public int CountSelected()
00442     {
00443        if (selected==null) return 0;
00444        int num=0;
00445        for (int n=0;n<mol.NumAtoms();n++) if (selected[n]) num++;
00446        return num;
00447     }
00448     
00449     // returns array of atoms which are presently selected, or everything if none
00450     public ArrayList<Integer> SelectedIndices()
00451     {
00452        ArrayList<Integer> selidx=new ArrayList<Integer>();
00453        if (selected!=null) for (int n=0;n<mol.NumAtoms();n++) if (selected[n]) selidx.add(n+1);
00454        if (selidx.size()==0) for (int n=1;n<=mol.NumAtoms();n++) selidx.add(n);
00455        return selidx;
00456     }
00457     
00458     // returns a subgraph of the molecule corresponding to selected atoms - or if none, the whole thing
00459     public Molecule SelectedSubgraph()
00460     {
00461        if (selected==null) return mol.Clone();
00462        int sum=0;
00463        for (int n=0;n<mol.NumAtoms();n++) if (selected[n]) sum++;
00464        if (sum==0) return mol.Clone();
00465 
00466        return mol.Subgraph(selected);
00467     }
00468     
00469     // deletes selected atoms, or all atoms if none selected
00470     public void DeleteSelected()
00471     {
00472        CacheUndo();
00473 
00474        boolean anySelected=false;
00475        if (selected!=null) for (int n=0;n<mol.NumAtoms();n++) if (selected[n]) {anySelected=true; break;}
00476        if (!anySelected) return;
00477 
00478        for (int n=mol.NumAtoms()-1;n>=0;n--) if (selected[n]) mol.DeleteAtomAndBonds(n+1);
00479 
00480        ClearTemporary();
00481        //ScaleToFit();
00482        DetermineSize();
00483        repaint();
00484        CheckDirtiness();
00485     }
00486     
00487     // switch between explicit/implicit modes; going to explicit marks current calculated value as absolute
00488     public void HydrogenSetExplicit(boolean IsExpl) {HydrogenSetExplicit(IsExpl,Molecule.HEXPLICIT_UNKNOWN);}
00489     public void HydrogenSetExplicit(boolean IsExpl,int NumExpl)
00490     {
00491        CacheUndo();
00492 
00493        ArrayList<Integer> sel=SelectedIndices();
00494        for (int n=0;n<sel.size();n++) 
00495        {
00496            int i=sel.get(n).intValue();
00497            if (IsExpl) mol.SetAtomHExplicit(i,mol.AtomHydrogens(i)); else mol.SetAtomHExplicit(i,NumExpl);
00498        }
00499        repaint();
00500 
00501        CheckDirtiness();
00502     }
00503     
00504     // any hydrogens which are implicit or explicit are actually created as nodes in the molecular graph; the explicit value of each
00505     // parent is set to unknown
00506     public void HydrogenCreateActual()
00507     {
00508        CacheUndo();
00509 
00510        ArrayList<Integer> sel=SelectedIndices();
00511        int score[]=new int[360];
00512        for (int n=0;n<sel.size();n++)
00513        {
00514            int i=sel.get(n).intValue();
00515            int hy=mol.AtomHydrogens(i);
00516            if (hy==0) continue;
00517            
00518            for (int j=0;j<360;j++) score[j]=0;
00519            int adj[]=mol.AtomAdjList(i);
00520            for (int j=0;j<adj.length;j++) 
00521            {
00522               int iang=(int)(Math.atan2(mol.AtomY(adj[j])-mol.AtomY(i),mol.AtomX(adj[j])-mol.AtomX(i))*180/Math.PI);
00523               if (iang<0) iang+=360;
00524               score[iang]=-1; score[(iang+1)%360]=-1; score[(iang+359)%360]=-1;
00525               int i0=(iang+180)%360,i1=(iang+120)%360,i2=(iang+240)%360;
00526               if (score[i0]>=0) score[i0]+=2;
00527               if (score[i1]>=0) score[i1]+=4;
00528               if (score[i2]>=0) score[i2]+=4;
00529            }
00530            
00531            while (hy>0)
00532            {
00533               int iang=0;
00534               for (int j=1;j<360;j++) if (score[j]>score[iang]) iang=j;
00535               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));
00536               mol.AddBond(i,num,1);
00537               score[iang]=-1; score[(iang+1)%360]=-1; score[(iang+359)%360]=-1;
00538               int i0=(iang+180)%360,i1=(iang+120)%360,i2=(iang+240)%360;
00539               if (score[i0]>=0) score[i0]++;
00540               if (score[i1]>=0) score[i1]+=2;
00541               if (score[i2]>=0) score[i2]+=2;
00542               hy--;
00543            }
00544            
00545            mol.SetAtomHExplicit(i,Molecule.HEXPLICIT_UNKNOWN);
00546        }
00547     
00548        ClearTemporary();
00549        DetermineSize();
00550        repaint();
00551 
00552        CheckDirtiness();
00553     }
00554     
00555     // of all the selected atoms and their neighbours, removes any which are element H
00556     public void HydrogenDeleteActual()
00557     {
00558        ArrayList<Integer> sel=SelectedIndices(),chop=new ArrayList<Integer>();
00559        for (int n=0;n<sel.size();n++)
00560        {
00561            int i=sel.get(n).intValue();
00562            if (mol.AtomElement(i).compareTo("H")==0) chop.add(new Integer(i));
00563            int adj[]=mol.AtomAdjList(i);
00564            for (int j=0;j<adj.length;j++) if (mol.AtomElement(adj[j]).compareTo("H")==0) chop.add(adj[j]);
00565        }
00566        
00567        if (chop.size()==0) return;
00568        CacheUndo();
00569        Collections.sort(chop);
00570        
00571        for (int n=0;n<chop.size();n++)
00572        {
00573            int adj[]=mol.AtomAdjList(chop.get(n).intValue());
00574            for (int i=0;i<adj.length;i++) mol.SetAtomHExplicit(adj[i],Molecule.HEXPLICIT_UNKNOWN);
00575        }
00576 
00577        int decr=0,lastVal=-1;
00578        for (int n=0;n<chop.size();n++)
00579        {
00580            int i=chop.get(n).intValue();
00581            if (i==lastVal) continue;
00582            mol.DeleteAtomAndBonds(i-decr);
00583            decr++;
00584            lastVal=i;
00585        }
00586     
00587        ClearTemporary();
00588        DetermineSize();
00589        repaint();
00590 
00591        CheckDirtiness();
00592     }
00593     
00594     // scale bond lengths to reasonable values (note: affects all atoms, selected or not)
00595     public void NormaliseBondLengths()
00596     {
00597        double numer=0,denom=0;
00598        for (int n=1;n<=mol.NumBonds();n++)
00599        {
00600            double dx=mol.AtomX(mol.BondFrom(n))-mol.AtomX(mol.BondTo(n)),dy=mol.AtomY(mol.BondFrom(n))-mol.AtomY(mol.BondTo(n));
00601            double weight=mol.BondInRing(n) ? 1 : 2;
00602            numer+=Math.sqrt(dx*dx+dy*dy)*weight;
00603            denom+=weight;
00604        }
00605        if (denom==0) return;
00606        
00607        CacheUndo();
00608        
00609        double stretch=IDEALBOND*denom/numer;
00610        for (int n=1;n<=mol.NumAtoms();n++)
00611        {
00612            mol.SetAtomPos(n,mol.AtomX(n)*stretch,mol.AtomY(n)*stretch);
00613        }
00614 
00615        
00616        ClearTemporary();
00617        DetermineSize();
00618        repaint();
00619     }
00620     
00621     // select next/prev atoms or connected components
00622     public void CycleSelection(boolean Forward,boolean Group)
00623     {
00624        if (mol.NumAtoms()<=1) return;
00625        
00626        int high=0;
00627        if (selected!=null) for (int n=1;n<=mol.NumAtoms();n++) if (selected[n-1])
00628        {
00629            if (Group) {if (mol.AtomConnComp(n)>high) high=mol.AtomConnComp(n);}
00630            else {high=n;}
00631        }
00632        int max=Group ? 0 : mol.NumAtoms();
00633        if (Group) for (int n=1;n<=mol.NumAtoms();n++) if (mol.AtomConnComp(n)>max) max=mol.AtomConnComp(n);
00634        
00635        int pos=Forward ? high+1 : high-1;
00636        if (pos<1) pos=max;
00637        if (pos>max) pos=1;
00638        
00639        selected=new boolean[mol.NumAtoms()];
00640        for (int n=1;n<=mol.NumAtoms();n++)
00641        {
00642            if (Group) {selected[n-1]=mol.AtomConnComp(n)==pos;}
00643            else {selected[n-1]=n==pos;}
00644        }
00645        
00646        ClearTemporary(false);
00647        repaint();
00648     }
00649     
00650     // move selected atoms by a small translation
00651     public void NudgeSelectedAtoms(double DX,double DY)
00652     {
00653        if (selected==null) return;
00654        CacheUndo();  
00655        for (int n=1;n<=mol.NumAtoms();n++) if (selected[n-1]) mol.SetAtomPos(n,mol.AtomX(n)+DX,mol.AtomY(n)+DY);
00656 
00657        ClearTemporary(false);
00658        DetermineSize();
00659        repaint();
00660     }
00661     
00662     // selected atoms are inverted about a mirror plane coincident with their centre of gravity
00663     public void FlipSelectedAtoms(boolean Vertical)
00664     {
00665        if (selected==null) return;
00666        
00667        int count=0;
00668        double cx=0,cy=0;
00669        for (int n=1;n<=mol.NumAtoms();n++) if (selected[n-1]) {cx+=mol.AtomX(n); cy+=mol.AtomY(n); count++;}
00670        if (count==0) return;
00671        
00672        CacheUndo();  
00673 
00674        cx/=count;
00675        cy/=count;
00676        for (int n=1;n<=mol.NumAtoms();n++) if (selected[n-1]) 
00677        {
00678            if (Vertical) mol.SetAtomPos(n,mol.AtomX(n),2*cy-mol.AtomY(n));
00679            else mol.SetAtomPos(n,2*cx-mol.AtomX(n),mol.AtomY(n));
00680        }
00681        
00682        ClearTemporary(false);
00683        DetermineSize();
00684        repaint();
00685     }
00686     
00687     // selected atoms are rotated about their centre of gravity
00688     public void RotateSelectedAtoms(double Degrees)
00689     {
00690        if (selected==null) return;
00691        
00692        int count=0;
00693        double cx=0,cy=0;
00694        for (int n=1;n<=mol.NumAtoms();n++) if (selected[n-1]) {cx+=mol.AtomX(n); cy+=mol.AtomY(n); count++;}
00695        if (count==0) return;
00696        
00697        CacheUndo();  
00698 
00699        cx/=count;
00700        cy/=count;
00701        double radians=Degrees*Math.PI/180;
00702        for (int n=1;n<=mol.NumAtoms();n++) if (selected[n-1]) 
00703        {
00704            double dx=mol.AtomX(n)-cx,dy=mol.AtomY(n)-cy;
00705            double dist=Math.sqrt(dx*dx+dy*dy),theta=Math.atan2(dy,dx);
00706            mol.SetAtomPos(n,cx+dist*Math.cos(theta+radians),cy+dist*Math.sin(theta+radians));
00707        }
00708 
00709        ClearTemporary(false);
00710        DetermineSize();
00711        repaint();
00712     }
00713     
00714     // changes stereochemistry; STEREO_UNKNOWN=invert, POS/NEG=set to this
00715     public void SetStereo(int Operation)
00716     {
00717        ArrayList<Integer> selidx=SelectedIndices();
00718 
00719        int[][] graph=new int[mol.NumAtoms()][];
00720        for (int n=0;n<mol.NumAtoms();n++) graph[n]=mol.AtomAdjList(n+1);
00721 
00722        CacheUndo();
00723 
00724        // chiral centres
00725        for (int n=0;n<selidx.size();n++) 
00726        {
00727            int a=selidx.get(n);
00728            int ster=mol.AtomChirality(a);
00729            if (Operation==Molecule.STEREO_UNKNOWN)
00730               {if (ster!=Molecule.STEREO_POS && ster!=Molecule.STEREO_NEG) continue;}
00731            else 
00732               {if (ster==Operation) continue;}
00733 
00734            // first the easy option: the atom already has chirality, can just flip all the wedges...
00735            if (ster==Molecule.STEREO_POS || ster==Molecule.STEREO_NEG)
00736            {
00737               for (int i=1;i<=mol.NumBonds();i++) 
00738                   if (mol.BondFrom(i)==a)
00739               {
00740                   if (mol.BondType(i)==Molecule.BONDTYPE_INCLINED) mol.SetBondType(i,Molecule.BONDTYPE_DECLINED);
00741                   else if (mol.BondType(i)==Molecule.BONDTYPE_DECLINED) mol.SetBondType(i,Molecule.BONDTYPE_INCLINED);
00742               }
00743               continue;
00744            }
00745            
00746            // not quite so easy: centre has no current chirality, and a specific enantiomer has been requested
00747            ArrayList<int[]> perm=WedgeFormations(a,Operation);
00748            if (perm!=null && perm.size()>0) // if anything available, use best...
00749            {
00750               int[] adj=mol.AtomAdjList(a);
00751               for (int i=0;i<adj.length;i++)
00752               {
00753                   int j=mol.FindBond(a,adj[i]); if (j==0) continue;
00754                   mol.SetBondType(j,perm.get(0)[i]<0 ? Molecule.BONDTYPE_DECLINED 
00755                                   : perm.get(0)[i]>0 ? Molecule.BONDTYPE_INCLINED : Molecule.BONDTYPE_NORMAL);
00756                   if (mol.BondFrom(j)!=a) mol.SetBondFromTo(j,mol.BondTo(j),mol.BondFrom(j));
00757               }
00758            }
00759        }
00760               
00761        // cis/trans 
00762        for (int n=1;n<=mol.NumBonds();n++) 
00763        {
00764            int bf=mol.BondFrom(n),bt=mol.BondTo(n);
00765            if (mol.BondOrder(n)==2 && selidx.indexOf(bf)<0 && selidx.indexOf(bt)<0) continue;
00766            int ster=mol.BondStereo(n);
00767            if ((ster!=Molecule.STEREO_POS && ster!=Molecule.STEREO_NEG) || ster==Operation) continue;
00768            if (mol.AtomRingBlock(bf)!=0 && mol.AtomRingBlock(bf)!=mol.AtomRingBlock(bt)) continue; // refuse to work with ring alkene
00769            
00770            // classify the sides of the X=Y bond by partitioning the component
00771            int sc1=1,sc2=1,side[]=new int[mol.NumAtoms()];
00772            for (int i=0;i<mol.NumAtoms();i++) side[i]=0;
00773            side[bf-1]=1; side[bt-1]=2;
00774            while (true)
00775            {
00776               boolean changed=false;
00777               for (int i=0;i<mol.NumAtoms();i++) if (side[i]==0)
00778                   for (int j=0;j<graph[i].length;j++) if (side[graph[i][j]-1]!=0) 
00779               {
00780                   side[i]=side[graph[i][j]-1];
00781                   if (side[i]==1) sc1++; else sc2++;
00782                   changed=true;
00783               }
00784               if (!changed) break;
00785            }
00786            int which=sc1<=sc2 ? 1 : 2;
00787            double cx=mol.AtomX(which==1 ? bf : bt),cy=mol.AtomY(which==1 ? bf : bt);
00788            double axis=Math.atan2(cy-mol.AtomY(which==1 ? bt : bf),cx-mol.AtomX(which==1 ? bt : bf));
00789            for (int i=0;i<mol.NumAtoms();i++) if (side[i]==which)
00790            {
00791               double dx=mol.AtomX(i+1)-cx,dy=mol.AtomY(i+1)-cy;
00792               double r=Math.sqrt(dx*dx+dy*dy),th=Math.atan2(dy,dx);
00793               th=2*axis-th;
00794               mol.SetAtomPos(i+1,cx+r*Math.cos(th),cy+r*Math.sin(th));
00795            }
00796            for (int i=1;i<=mol.NumBonds();i++) 
00797               if (mol.BondType(i)==Molecule.BONDTYPE_INCLINED || mol.BondType(i)==Molecule.BONDTYPE_DECLINED)
00798                   if (side[mol.BondFrom(i)-1]==which && side[mol.BondTo(i)-1]==which)
00799                      mol.SetBondType(i,mol.BondType(i)==Molecule.BONDTYPE_INCLINED ? Molecule.BONDTYPE_DECLINED 
00800                                                                             : Molecule.BONDTYPE_INCLINED);
00801        }
00802 
00803        ClearTemporary(false);
00804        DetermineSize();
00805        repaint();
00806     }
00807     
00808     // selected chiral centres lose their wedge bonds
00809     public void RemoveChiralWedges()
00810     {
00811        CacheUndo();
00812     
00813        ArrayList<Integer> selidx=SelectedIndices();
00814        for (int n=0;n<selidx.size();n++) if (mol.AtomChirality(selidx.get(n))!=Molecule.STEREO_NONE)
00815        {
00816            for (int i=1;i<=mol.NumBonds();i++) 
00817               if ((mol.BondFrom(i)==selidx.get(n) || mol.BondTo(i)==selidx.get(n)) && 
00818                   (mol.BondType(i)==Molecule.BONDTYPE_INCLINED || mol.BondType(i)==Molecule.BONDTYPE_DECLINED))
00819               mol.SetBondType(i,Molecule.BONDTYPE_NORMAL);
00820        }
00821        repaint();
00822     }
00823 
00824     // for any chiral centres, pick the next set of valid wedge bonds
00825     public void CycleChiralWedges()
00826     {
00827        CacheUndo();
00828     
00829        ArrayList<Integer> selidx=SelectedIndices();
00830        for (int n=0;n<selidx.size();n++)
00831        {
00832            int a=selidx.get(n),chi=mol.AtomChirality(a);
00833            if (chi!=Molecule.STEREO_POS && chi!=Molecule.STEREO_NEG) continue;
00834            ArrayList<int[]> perm=WedgeFormations(a,chi);
00835            if (perm.size()<=1) continue; // invalid or no point
00836            
00837            int[] adj=mol.AtomAdjList(a),curperm=new int[adj.length];
00838            for (int i=0;i<adj.length;i++)
00839            {
00840               int j=mol.FindBond(a,adj[i]);
00841               curperm[i]=mol.BondType(j)==Molecule.BONDTYPE_INCLINED ? 1 : mol.BondType(j)==Molecule.BONDTYPE_DECLINED ? -1 : 0;
00842            }
00843            int match=-1;
00844            for (int i=0;i<perm.size();i++)
00845            {
00846               int[] thisperm=perm.get(i);
00847               boolean same=true;
00848               for (int j=0;j<curperm.length;j++) if (thisperm[j]!=curperm[j]) {same=false; break;}
00849               if (same) {match=i; break;}
00850            }
00851            match=(match+1)%perm.size();
00852            curperm=perm.get(match);
00853 
00854            for (int i=0;i<adj.length;i++)
00855            {
00856               int j=mol.FindBond(a,adj[i]);
00857               if (mol.BondFrom(j)!=a) mol.SetBondFromTo(j,a,adj[i]);
00858               mol.SetBondType(j,curperm[i]<0 ? Molecule.BONDTYPE_DECLINED 
00859                              : curperm[i]>0 ? Molecule.BONDTYPE_INCLINED : Molecule.BONDTYPE_NORMAL);
00860            }
00861        }
00862        repaint();
00863     }
00864     
00865     // ------------------ private functions --------------------
00866 
00867     // translation of screen & molecule coordinates    
00868     double AngToX(double AX) {return (offsetX+AX)*scale;}
00869     double AngToY(double AY) {return (offsetY-AY)*scale;}
00870     double XToAng(double PX) {return (PX/scale)-offsetX;}
00871     double YToAng(double PY) {return (-PY/scale)+offsetY;}
00872 
00873     // resizes the widget, which is assumed scrollable, to fit the current magnification of the whole molecule
00874     void DetermineSize()
00875     {
00876        int w,h;
00877        if (!isSelectionPane)
00878        {
00879            w=Math.max((int)AngToX(mol.MaxX()+1),500);
00880            h=Math.max((int)AngToY(mol.MinY()-1),500);
00881        }
00882        else
00883        {
00884            w=selBoxW;
00885            h=selBoxH;
00886        }
00887        setPreferredSize(new Dimension(w,h));
00888        setSize(w,h);
00889     }
00890     
00891     // erases some of the datastructures used for caching the drawing elements
00892     void ClearTemporary() {ClearTemporary(true);}
00893     void ClearTemporary(boolean AndSelected)
00894     {
00895        px=py=rw=rh=null;
00896        highlightAtom=highlightBond=0;
00897        if (AndSelected) selected=null; 
00898        else if (selected!=null && selected.length!=mol.NumAtoms())
00899        {
00900            boolean newSelected[]=new boolean[mol.NumAtoms()];
00901            for (int n=0;n<selected.length;n++) newSelected[n]=selected[n];
00902            selected=newSelected;
00903        }
00904     }
00905     
00906     void ResetSelected(boolean Clear)
00907     {
00908        if (selected==null) selected=new boolean[mol.NumAtoms()];
00909        if (Clear) for (int n=0;n<mol.NumAtoms();n++) selected[n]=false;
00910     }
00911     
00912     // return the atom underneath the given position, in screen coordinates; assumes that the appropriate arrays of size and position
00913     // have been filled out
00914     int PickAtom(int X,int Y)
00915     {
00916        if (px==null || py==null) return 0; //DefinePositions()...?;
00917        
00918        for (int n=1;n<=mol.NumAtoms();n++) 
00919        {
00920            double dx=X-px[n-1],dy=Y-py[n-1];
00921            if (Math.abs(dx)<=rw[n-1] && Math.abs(dy)<=rh[n-1])
00922               if (dx*dx/(rw[n-1]*rw[n-1])+dy*dy/(rh[n-1]*rh[n-1])<=1) {return n;}
00923        }
00924        return 0;
00925     }
00926     
00927     // returns the bond underneath the screen position
00928     int PickBond(int X,int Y)
00929     {
00930        if (px==null || py==null) return 0;
00931     
00932        for (int n=1;n<=mol.NumBonds();n++)
00933        {
00934            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];
00935 
00936            double nx1=x1,ny1=y1,nx2=x2,ny2=y2;
00937            int delta=Math.max(2,(int)(scale/20));
00938            if (nx1>nx2) {nx1=x2; nx2=x1;}
00939            if (ny1>ny2) {ny1=y2; ny2=y1;}
00940            if (X<nx1-2*delta || X>nx2+2*delta || Y<ny1-2*delta || Y>ny2+2*delta) continue;
00941 
00942            double dx=x2-x1,dy=y2-y1,d;
00943            if (Math.abs(dx)>Math.abs(dy)) d=Y-y1-(X-x1)*dy/dx; else d=X-x1-(Y-y1)*dx/dy;
00944            if (Math.abs(d)>(2+mol.BondOrder(n))*delta) continue;
00945            return n;
00946        }
00947        return 0;
00948     }
00949     
00950     // snaps the draw-to-position to multiples of 30 degrees
00951     void SnapToolBond()
00952     {
00953        double cx=toolBondFrom>0 ? mol.AtomX(toolBondFrom) : toolBondFromX;
00954        double cy=toolBondFrom>0 ? mol.AtomY(toolBondFrom) : toolBondFromY;
00955        double dx=toolBondToX-cx,dy=toolBondToY-cy;
00956        double th=Math.atan2(dy,dx)*180/Math.PI,ext=Math.sqrt(dx*dx+dy*dy);
00957        th=(Math.round(th/30)*30)*Math.PI/180;
00958        ext=Math.round(ext/IDEALBOND)*IDEALBOND;
00959        toolBondToX=cx+ext*Math.cos(th);
00960        toolBondToY=cy+ext*Math.sin(th);
00961     }
00962         
00963     // should be called before any unit operation is conducted; the current molecule state is stored in the undo buffer
00964     void CacheUndo()
00965     {
00966        // !! check to see if the last molecule in the cache is literally identical, and if so, do nothing
00967        if (undo==null) undo=new EditState[UNDO_LEVELS];
00968        redo=null;
00969        for (int n=UNDO_LEVELS-1;n>0;n--) undo[n]=undo[n-1];
00970        undo[0]=new EditState();
00971        undo[0].Molecule=mol==null ? null : mol.Clone();
00972        undo[0].Selected=selected==null ? null : selected.clone();
00973     }
00974     
00975     // called when the element editing widget has ended its lifecycle, and the change is to be applied
00976     void CompleteAtomEdit()
00977     {
00978        if (toolAtomEditBox==null) return;
00979        String el=toolAtomEditBox.getText();
00980        if (el.length()>0)
00981        {
00982            CacheUndo();
00983 
00984            if (el.charAt(0)>='a' && el.charAt(0)<='z') el=el.substring(0,1).toUpperCase()+el.substring(1);
00985 
00986            if (toolAtomEditSel==0)
00987            {
00988               mol.AddAtom(el,XToAng(toolAtomEditX),YToAng(toolAtomEditY));
00989               ClearTemporary();
00990               DetermineSize();
00991            }
00992            else mol.SetAtomElement(toolAtomEditSel,el);
00993        }
00994        
00995        toolAtomEditBox.setVisible(false);
00996        remove(toolAtomEditBox);
00997        toolAtomEditBox=null;
00998        
00999        repaint();
01000        CheckDirtiness();
01001     }
01002     
01003     // the currently active template is rotated according to a mapping between atoms
01004     void AdjustTemplateByAtom(int Atom)
01005     {
01006        templDraw=template.Clone();
01007        
01008        ArrayList<Integer> bonded=new ArrayList<Integer>();
01009        for (int n=1;n<=mol.NumBonds();n++)
01010        {
01011            if (mol.BondFrom(n)==Atom) bonded.add(new Integer(mol.BondTo(n)));
01012            if (mol.BondTo(n)==Atom) bonded.add(new Integer(mol.BondFrom(n)));
01013        }
01014        
01015        final int INCR=1;
01016        double[] rotScores=new double[360/INCR];
01017        for (int n=1;n<=templDraw.NumAtoms();n++) if (n!=templateIdx)
01018        {
01019            double x=template.AtomX(n)-template.AtomX(templateIdx),y=template.AtomY(n)-template.AtomY(templateIdx);
01020            double th=Math.atan2(y,x),ext=Math.sqrt(x*x+y*y);
01021            for (int i=0;i<(360/INCR);i++)
01022            {
01023               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);
01024               for (int j=0;j<bonded.size();j++)
01025               {
01026                   int k=bonded.get(j).intValue();
01027                   double dx=mol.AtomX(k)-rx,dy=mol.AtomY(k)-ry;
01028                   double ext2=dx*dx+dy*dy;
01029                   if (ext2<0.01) ext2=0.01;
01030                   rotScores[i]+=1/ext2;
01031               }
01032            }
01033        }
01034        
01035        int bestRot=0;
01036        for (int n=1;n<(360/INCR);n++) if (rotScores[n]<rotScores[bestRot]) bestRot=n;
01037        
01038        for (int n=1;n<=templDraw.NumAtoms();n++)
01039        {
01040            double x=template.AtomX(n)-template.AtomX(templateIdx),y=template.AtomY(n)-template.AtomY(templateIdx);
01041            double th=Math.atan2(y,x),ext=Math.sqrt(x*x+y*y);
01042            templDraw.SetAtomPos(n,mol.AtomX(Atom)+ext*Math.cos(th+bestRot*INCR*Math.PI/180),
01043                                mol.AtomY(Atom)+ext*Math.sin(th+bestRot*INCR*Math.PI/180));
01044        }
01045     }
01046     
01047     // the currently active template is rotated according to a mapping between bonds
01048     boolean AdjustTemplateByBond(int Bond)
01049     {
01050        Molecule[] rotMol=new Molecule[2];
01051        double[] rotScores=new double[2];
01052        
01053        for (int r=0;r<2;r++)
01054        {
01055            rotMol[r]=template.Clone();
01056            int imol1=r==0 ? mol.BondFrom(Bond) : mol.BondTo(Bond),imol2=r==0 ? mol.BondTo(Bond) : mol.BondFrom(Bond);
01057            int irot1=template.BondFrom(-templateIdx),irot2=template.BondTo(-templateIdx);
01058            double dtheta=Math.atan2(mol.AtomY(imol2)-mol.AtomY(imol1),mol.AtomX(imol2)-mol.AtomX(imol1))
01059                       -Math.atan2(template.AtomY(irot2)-template.AtomY(irot1),template.AtomX(irot2)-template.AtomX(irot1));
01060            
01061            for (int n=1;n<=template.NumAtoms();n++)
01062            {
01063               double rx=template.AtomX(n)-template.AtomX(irot1),ry=template.AtomY(n)-template.AtomY(irot1);
01064               double th=Math.atan2(ry,rx),ext=Math.sqrt(rx*rx+ry*ry);
01065               rx=mol.AtomX(imol1)+ext*Math.cos(th+dtheta);
01066               ry=mol.AtomY(imol1)+ext*Math.sin(th+dtheta);
01067               rotMol[r].SetAtomPos(n,rx,ry);
01068               
01069               for (int i=1;i<=mol.NumAtoms();i++)
01070               {
01071                   double dx=mol.AtomX(i)-rx,dy=mol.AtomY(i)-ry;
01072                   double ext2=dx*dx+dy*dy;
01073                   if (ext2<0.01) ext2=0.01;
01074                   rotScores[r]+=1/ext2;
01075               }
01076            }
01077        }
01078        
01079        boolean swap=rotScores[0]<rotScores[1];
01080        templDraw=rotMol[swap ? 0 : 1];
01081        return swap;
01082     }
01083     
01084     // the currently active template is merely translated, as there is no current atom or bond mapping
01085     void AdjustTemplateByCoord(double X,double Y)
01086     {
01087        templDraw=template.Clone();
01088 
01089        double dx=0,dy=0;
01090        if (templateIdx>0) {dx=template.AtomX(templateIdx); dy=template.AtomY(templateIdx);}
01091        else if (templateIdx<0)
01092        {
01093            int from=template.BondFrom(-templateIdx),to=template.BondTo(-templateIdx);
01094            dx=0.5*(template.AtomX(from)+template.AtomX(to));
01095            dy=0.5*(template.AtomY(from)+template.AtomY(to));
01096        }
01097        for (int n=1;n<=template.NumAtoms();n++) templDraw.SetAtomPos(n,template.AtomX(n)-dx+X,template.AtomY(n)-dy+Y);
01098     }
01099     
01100     // places a template, where atoms are mapped
01101     void TemplateSetByAtom(int JoinAtom)
01102     {
01103        int[] map=new int[templDraw.NumAtoms()];
01104        int oldNum=mol.NumAtoms();
01105        for (int n=1;n<=templDraw.NumAtoms();n++) if (JoinAtom==0 || n!=templateIdx)
01106        {
01107            mol.AddAtom(templDraw.AtomElement(n),templDraw.AtomX(n),templDraw.AtomY(n),
01108                      templDraw.AtomCharge(n),templDraw.AtomUnpaired(n));
01109        }
01110        for (int n=1;n<=templDraw.NumBonds();n++)
01111        {
01112            int from=templDraw.BondFrom(n);
01113            int to=templDraw.BondTo(n);
01114            if (JoinAtom>0)
01115            {
01116               if (from==templateIdx) from=JoinAtom; 
01117               else 
01118               {
01119                   if (from>templateIdx) from--;
01120                   from+=oldNum;
01121               }
01122               if (to==templateIdx) to=JoinAtom; 
01123               else 
01124               {
01125                   if (to>templateIdx) to--;
01126                   to+=oldNum;
01127               }
01128            }
01129            else {from+=oldNum; to+=oldNum;}
01130            mol.AddBond(from,to,templDraw.BondOrder(n),templDraw.BondType(n));
01131        }
01132 
01133        MergeNewAtoms(oldNum);
01134 
01135        ClearTemporary();
01136        DetermineSize();
01137        repaint();
01138     }
01139     
01140     // places a template, where bonds are mapped
01141     void TemplateSetByBond(int JoinBond,boolean Swap)
01142     {
01143        int[] map=new int[templDraw.NumAtoms()];
01144        int oldNum=mol.NumAtoms();
01145        int joinFrom=JoinBond>0 ? mol.BondFrom(JoinBond) : 0,joinTo=JoinBond>0 ? mol.BondTo(JoinBond) : 0;
01146        int newFrom=Swap ? templDraw.BondFrom(-templateIdx) : templDraw.BondTo(-templateIdx);
01147        int newTo=Swap ? templDraw.BondTo(-templateIdx) : templDraw.BondFrom(-templateIdx); 
01148        for (int n=1;n<=templDraw.NumAtoms();n++)
01149        {
01150            if (n==newFrom && JoinBond>0) map[n-1]=joinFrom;
01151            else if (n==newTo && JoinBond>0) map[n-1]=joinTo;
01152            else 
01153            {
01154               map[n-1]=mol.AddAtom(templDraw.AtomElement(n),templDraw.AtomX(n),templDraw.AtomY(n),
01155                                  templDraw.AtomCharge(n),templDraw.AtomUnpaired(n));
01156            }
01157        }
01158        for (int n=1;n<=template.NumBonds();n++) if (n!=-templateIdx || JoinBond==0)
01159        {
01160            mol.AddBond(map[templDraw.BondFrom(n)-1],map[templDraw.BondTo(n)-1],templDraw.BondOrder(n),templDraw.BondType(n));
01161        }
01162        
01163        MergeNewAtoms(oldNum);
01164        
01165        ClearTemporary();
01166        DetermineSize();
01167        repaint();
01168     }
01169     
01170     // any atoms of index greater than the watermark are merged with previously defined atoms if they are close
01171     void MergeNewAtoms(int Watermark)
01172     {
01173        int pos=Watermark+1;
01174        while (pos<=mol.NumAtoms())
01175        {
01176            int close=0;
01177            for (int n=1;n<=Watermark;n++) 
01178            {
01179               double dx=mol.AtomX(n)-mol.AtomX(pos),dy=mol.AtomY(n)-mol.AtomY(pos);
01180               if (dx*dx+dy*dy<0.2*0.2) {close=n; break;}
01181            }
01182            if (close>0)
01183            {
01184               int[] adj=mol.AtomAdjList(pos);
01185               for (int i=0;i<adj.length;i++) if (mol.FindBond(close,adj[i])==0)
01186               {
01187                   int j=mol.FindBond(pos,adj[i]);
01188                   mol.AddBond(close,adj[i],mol.BondOrder(j));
01189               }
01190               mol.DeleteAtomAndBonds(pos);
01191            }
01192            else pos++;
01193        }
01194     }
01195 
01196     // returns true if there are any selected atoms
01197     boolean AnySelected()
01198     {
01199        if (selected==null) return false;
01200        for (int n=0;n<mol.NumAtoms();n++) if (selected[n]) return true;
01201        return false;
01202     }
01203 
01204     double DragExtendBy(double px,double py)
01205     {
01206        double diff=0.2*Math.sqrt(px*px+py*py)/scale;
01207        if (px<0 && py<0) diff=-diff;
01208        
01209        if (diff>=0) return 1+diff;
01210        else return Math.exp(diff);
01211     }
01212     
01213     // calculate all the wedge bond formations for a given atom for a given chirality (+/-), ranked in order, null if none
01214     ArrayList<int[]> WedgeFormations(int N,int Chi)
01215     {
01216        if (mol.AtomAdjCount(N)!=3 && mol.AtomAdjCount(N)!=4) return null;
01217        int[] adj=mol.AtomAdjList(N);
01218        for (int i=0;i<adj.length-1;i++) for (int j=i+1;j<adj.length;j++) 
01219            if (mol.AtomPriority(adj[i])==mol.AtomPriority(adj[j])) return null;
01220 
01221        int[] badj=new int[adj.length];
01222        for (int n=0;n<adj.length;n++) badj[n]=mol.FindBond(N,adj[n]);
01223        
01224        ArrayList<int[]> perm=new ArrayList<int[]>();
01225 
01226        // generate all possible sensible wedge combinations
01227        if (adj.length==3)
01228        {
01229            for (int i=0;i<3;i++) for (int iz=-1;iz<=1;iz+=2)
01230            {
01231               int[] wedges=new int[3];
01232               for (int n=0;n<3;n++) wedges[n]=0;
01233               wedges[i]=iz;
01234               perm.add(wedges);
01235            }
01236        }
01237        else
01238        {
01239            for (int i=0;i<4;i++) for (int iz=-1;iz<=1;iz+=2)
01240            {
01241               int[] wedges=new int[4];
01242               for (int n=0;n<4;n++) wedges[n]=0;
01243               wedges[i]=iz;
01244               perm.add(wedges);
01245               
01246               for (int j=i+1;j<4;j++) for (int jz=-1;jz<=1;jz+=2)
01247               {
01248                   if (/*i==j || */jz==iz) continue;
01249                   wedges=new int[4];
01250                   for (int n=0;n<4;n++) wedges[n]=0;
01251                   wedges[i]=iz;
01252                   wedges[j]=jz;
01253                   perm.add(wedges);
01254               }
01255            }
01256        }
01257 
01258        // keep only the ones which indicate the desired enantiomer
01259        int pos=0;
01260        while (pos<perm.size())
01261        {
01262            int[] wedges=perm.get(pos);
01263            Molecule mchi=mol.Clone();
01264            for (int n=0;n<adj.length;n++) 
01265            {
01266               mchi.SetBondType(badj[n],wedges[n]<0 ? Molecule.BONDTYPE_DECLINED 
01267                                    : wedges[n]>0 ? Molecule.BONDTYPE_INCLINED : Molecule.BONDTYPE_NORMAL);
01268               if (mchi.BondFrom(badj[n])!=N) mol.SetBondFromTo(badj[n],mol.BondTo(badj[n]),mol.BondFrom(badj[n]));
01269 
01270            }
01271            if (mchi.AtomChirality(N)!=Chi) perm.remove(pos); else pos++;
01272        }
01273        
01274        // score each one based on crude aesthetic criteria
01275        double[] score=new double[perm.size()];
01276        for (int n=0;n<perm.size();n++)
01277        {
01278            score[n]=0;
01279            int[] wedges=perm.get(n);
01280            int wcount=0;
01281            for (int i=0;i<adj.length;i++) if (wedges[i]!=0)
01282            {
01283               wcount++;
01284               score[n]-=0.5*mol.AtomPriority(adj[i])/mol.NumAtoms();
01285               if (mol.AtomAdjCount(adj[i])==1) score[n]++;
01286               if (mol.AtomRingBlock(adj[i])>0) 
01287               {
01288                   score[n]--;
01289                   if (mol.AtomRingBlock(N)==mol.AtomRingBlock(adj[i])) score[n]--;
01290               }
01291            }
01292            if (adj.length==4 && wcount==2) score[n]++;
01293        }
01294        
01295        // sort best-first
01296        pos=0;
01297        while (pos<perm.size()-1)
01298        {
01299            if (score[pos]<score[pos+1])
01300            {
01301               int[] w1=perm.get(pos),w2=perm.get(pos+1);
01302               perm.set(pos+1,w1);
01303               perm.set(pos,w2);
01304               double s=score[pos]; score[pos]=score[pos+1]; score[pos+1]=s;
01305               if (pos>0) pos--;
01306            } else pos++;
01307        }
01308        
01309        
01310        /*System.out.println("PERMSIZE:"+perm.size());
01311        for (int n=0;n<perm.size();n++)
01312        {
01313            int[] w=perm.get(n);
01314            System.out.print("n="+n);
01315            for (int i=0;i<w.length;i++) System.out.print(" "+w[i]);
01316            System.out.println(" score="+score[n]);
01317        }*/
01318 
01319        return perm;
01320     }
01321     
01322     // ------------------ event functions --------------------
01323     
01324     // the paint function sets up the basic graphics drawing context, then performs some preliminary transformations of any tools effects
01325     // that are in progress, hands off all the work to DrawMolecule, then fetches back some of data determined in situ
01326     
01327     protected void paintComponent(Graphics gr) 
01328     {
01329        if (autoScale) ScaleToFit();
01330        // this is the canvas background
01331        Color b=new Color(255,255,255);
01332        gr.setColor(b);
01333 //     gr.setColor(Color.WHITE);
01334        gr.fillRect(0,0,getWidth(),getHeight());
01335        if (hasBorder)
01336        {
01337            gr.setColor(Color.BLACK);
01338            gr.drawRect(0,0,getWidth()-1,getHeight()-1);
01339        }
01340 
01341        Graphics2D g=(Graphics2D)gr;
01342        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
01343        DrawMolecule draw=new DrawMolecule(mol,g);
01344        
01345        // provide a miscellany of editing data to the drawing class, so that it can represent everything that is going on
01346        // draw is for chemical/binding: 
01347        draw.SetBackground(getBackground());
01348        draw.SetShowHydr(showHydr);
01349        draw.SetShowMode(showMode);
01350        draw.SetShowStereo(showSter);
01351        draw.SetTransform(offsetX,offsetY,scale);
01352        draw.SetHighlight(highlightAtom,highlightBond);
01353        
01354        ResetSelected(false);
01355        draw.SetSelected(selected,dragged);
01356 
01357        if ((tool==TOOL_ATOM && toolAtomDrag) || (tool==TOOL_BOND && toolBondFrom>0))
01358            draw.BondInProgress(toolBondFrom,toolBondToX,toolBondToY,toolBondOrder,toolBondType);
01359        if (tool==TOOL_ATOM && toolAtomDrag && toolAtomType!=null && toolAtomType.compareTo("C")!=0) 
01360            draw.AtomInProgress(toolAtomType,toolBondToX,toolBondToY);
01361        if (tool==TOOL_BOND && toolBondFrom==0 && toolBondDrag)
01362        {
01363            int i=PickAtom((int)AngToX(toolBondToX),(int)AngToY(toolBondToY));
01364            if (i==0 && toolSnap) SnapToolBond();
01365            double x1=toolBondFromX,y1=toolBondFromY,x2=toolBondToX,y2=toolBondToY;
01366            if (i>0) {x2=mol.AtomX(i); y2=mol.AtomY(i);} 
01367            draw.NewBondLine(x1,y1,x2,y2);
01368        }
01369        if (toolDragReason==DRAG_SELECT)
01370        {
01371            draw.DragSelect((int)toolDragX1,(int)toolDragY1,(int)toolDragX2,(int)toolDragY2);
01372        }
01373        if ((toolDragReason==DRAG_MOVE || toolDragReason==DRAG_COPY || toolDragReason==DRAG_SCALE) 
01374            && (toolDragX1!=toolDragX2 || toolDragY1!=toolDragY2))
01375        {
01376            if (toolDragReason==DRAG_SCALE)
01377            {
01378               double extmul=DragExtendBy(toolDragX2-toolDragX1,toolDragY2-toolDragY1),cx=0,cy=0;
01379               int count=0;
01380               for (int n=1;n<=mol.NumAtoms();n++) if (selected[n-1]) {cx+=mol.AtomX(n); cy+=mol.AtomY(n); count++;}
01381               cx/=count; cy/=count;
01382               draw.DragScale(cx,cy,extmul);
01383            }
01384            else
01385            {
01386               int dx=(int)(toolDragX2-toolDragX1),dy=(int)(toolDragY2-toolDragY1);
01387               draw.DragMove(dx,dy,toolDragReason==DRAG_COPY);
01388            }
01389        }
01390        if (toolDragReason==DRAG_ROTATE && (Math.abs(toolDragX2-toolDragX1)>5 || Math.abs(toolDragY2-toolDragY1)>5))
01391        {
01392            double dx=toolDragX2-toolDragX1,dy=toolDragY2-toolDragY1;
01393            double th=-Math.atan2(dy,dx)*180/Math.PI;
01394            if (toolSnap) th=Math.round(th/15)*15;
01395            
01396            draw.DragRotate(th,(int)toolDragX1,(int)toolDragY1);
01397        }
01398        if (tool==TOOL_TEMPLATE && trackX>=0 && trackY>=0)
01399        {
01400            if (highlightAtom!=0 && templateIdx>0) AdjustTemplateByAtom(highlightAtom);
01401            else if (highlightBond!=0 && templateIdx<0) AdjustTemplateByBond(highlightBond);
01402            else AdjustTemplateByCoord(XToAng(trackX),YToAng(trackY));
01403 
01404            draw.OutlineTemplate(templDraw);
01405        }
01406 
01407        draw.Draw();
01408 
01409        // fetch some of the calculated properties, which are used for the editing progress
01410        px=draw.GetPX(); py=draw.GetPY(); rw=draw.GetRW(); rh=draw.GetRH();
01411        bfx=draw.GetBFX(); bfy=draw.GetBFY(); btx=draw.GetBTX(); bty=draw.GetBTY();
01412     }
01413     
01414     // Mouse events: the callbacks for Clicked, Pressed, Released, Dragged and Moved form a slightly complicated interplay of the various
01415     // tool events. The 'tool' variable, and its various permitted values, should make most of the behaviour reasonably self-explanatory;
01416     // note that many of the tools have multiple functions which may be sprinkled around the various event callbacks.
01417     
01418     public void mouseClicked(MouseEvent e)
01419     {
01420        if (tool==TOOL_CURSOR && selectListen!=null)
01421        {
01422            int i=PickAtom(e.getX(),e.getY());
01423            if ((e.getModifiers()&MouseEvent.CTRL_MASK)>0 && i>0 && editable) // select connected component
01424            {
01425               if ((e.getModifiers()&MouseEvent.SHIFT_MASK)==0 && selected!=null) for (int n=0;n<mol.NumAtoms();n++) selected[n]=false;
01426               if (selected==null) selected=new boolean[mol.NumAtoms()];
01427               int cc=mol.AtomConnComp(i);
01428               for (int n=1;n<=mol.NumAtoms();n++) if (mol.AtomConnComp(n)==cc) selected[n-1]=true;
01429               repaint();
01430            }
01431            else if (i>0) selectListen.MolSelected(this,i,e.getClickCount()>1); // notify of atom selection
01432            else // notify of bond (or nothing) selection
01433            {
01434               i=PickBond(e.getX(),e.getY());
01435               /*if (i>0)*/ selectListen.MolSelected(this,-i,e.getClickCount()>1); 
01436               // (0==clicked in general area)
01437            }
01438        }
01439        else if (tool==TOOL_ROTATOR) // deselect
01440        {
01441            selected=null;
01442            ClearTemporary();
01443            repaint();
01444        }
01445        else if (tool==TOOL_ERASOR) // delete something, be it atom or bond
01446        {
01447            int i=PickAtom(e.getX(),e.getY());
01448            if (i>0) 
01449            {
01450               CacheUndo();
01451               mol.DeleteAtomAndBonds(i);
01452            }
01453            else
01454            {
01455               CacheUndo();
01456               i=PickBond(e.getX(),e.getY());
01457               if (i>0) mol.DeleteBond(i);
01458            }
01459            if (i>0)
01460            {
01461               ClearTemporary();
01462               DetermineSize();
01463               repaint();
01464            }
01465        }
01466        else if (tool==TOOL_ATOM && e.getButton()==MouseEvent.BUTTON1 && !toolAtomDrag)
01467        {
01468            if (toolAtomEditBox!=null) 
01469            {
01470               CompleteAtomEdit();
01471               return;
01472            }
01473        
01474            if (toolAtomType!=null)  // add new atom, or change element label
01475            {
01476               int i=PickAtom(e.getX(),e.getY());
01477               CacheUndo();
01478               if (i==0) 
01479               {
01480                   i=mol.AddAtom(toolAtomType,XToAng(e.getX()),YToAng(e.getY()));
01481                   offsetX=e.getX()/scale-mol.AtomX(i);
01482                   offsetY=e.getY()/scale+mol.AtomY(i);
01483               }
01484               else mol.SetAtomElement(i,toolAtomType);
01485               ClearTemporary();
01486               DetermineSize();
01487               repaint();
01488            } 
01489            else // setup new editing box for element
01490            {
01491               toolAtomEditX=e.getX(); toolAtomEditY=e.getY();
01492               toolAtomEditSel=PickAtom(toolAtomEditX,toolAtomEditY);
01493               if (toolAtomEditSel==0 && PickBond(e.getX(),e.getY())>0) return;
01494               
01495               toolAtomEditBox=new JTextField(toolAtomEditSel>0 ? mol.AtomElement(toolAtomEditSel) : "");
01496               add(toolAtomEditBox);
01497               toolAtomEditBox.addFocusListener(this);
01498               toolAtomEditBox.addKeyListener(this);
01499               toolAtomEditBox.setLocation(toolAtomEditX-10,toolAtomEditY-10);
01500               toolAtomEditBox.setSize(20,20);
01501               toolAtomEditBox.setVisible(true);
01502               toolAtomEditBox.setSelectionStart(0);
01503               toolAtomEditBox.setSelectionEnd(toolAtomEditBox.getText().length());
01504               toolAtomEditBox.grabFocus();
01505            }
01506        }
01507        else if (tool==TOOL_TEMPLATE && e.getButton()==MouseEvent.BUTTON2) // flip the template, horizontal or vertical
01508        {
01509            boolean vertical=e.isShiftDown();
01510            for (int n=1;n<=template.NumAtoms();n++) 
01511               template.SetAtomPos(n,template.AtomX(n)*(vertical?1:-1),template.AtomY(n)*(vertical?-1:1));
01512            templDraw=template.Clone();
01513            repaint();
01514        }      
01515 
01516        CheckDirtiness();
01517     }
01518     
01519     public void mouseEntered(MouseEvent e) 
01520     {
01521        boolean redraw=false;
01522        if (tool==TOOL_TEMPLATE && (trackX!=e.getX() || trackY!=e.getY())) redraw=true;
01523        trackX=e.getX(); trackY=e.getY();
01524        if (redraw) repaint();
01525     }
01526     
01527     public void mouseExited(MouseEvent e) 
01528     {
01529        boolean redraw=false;
01530        if (tool==TOOL_TEMPLATE && (trackX!=e.getX() || trackY!=e.getY())) redraw=true;
01531        trackX=-1; trackY=-1;
01532        if (redraw) repaint();
01533     }
01534     
01535     public void mousePressed(MouseEvent e)
01536     {
01537        grabFocus();
01538 
01539        if ((tool==TOOL_CURSOR || (tool==TOOL_ROTATOR && !AnySelected())) && e.getButton()==MouseEvent.BUTTON1 && editable)
01540        { // consider initiating a drag of the select, or translate position variety
01541            highlightAtom=highlightBond=0;
01542            boolean shift=(e.getModifiers()&MouseEvent.SHIFT_MASK)>0;
01543            boolean ctrl=(e.getModifiers()&MouseEvent.CTRL_MASK)>0;
01544            boolean alt=(e.getModifiers()&MouseEvent.ALT_MASK)>0;
01545            boolean anySelected=CountSelected()>0;
01546            if (tool==TOOL_ROTATOR) {shift=false; ctrl=false; alt=false;} // can only select with rotator
01547            if (!ctrl && !alt) 
01548            {
01549               ResetSelected(!shift);
01550               int atom=PickAtom(e.getX(),e.getY());
01551               if (atom>0) selected[atom-1]=true;
01552               else toolDragReason=DRAG_SELECT;
01553            }
01554            else if (!shift && ctrl && !alt && anySelected) toolDragReason=DRAG_COPY;
01555            else if (!shift && !ctrl && alt && anySelected) toolDragReason=DRAG_MOVE;
01556            else if (shift && !ctrl && alt && anySelected) toolDragReason=DRAG_SCALE;
01557 
01558            toolDragX1=toolDragX2=e.getX();
01559            toolDragY1=toolDragY2=e.getY();
01560            repaint();
01561        }
01562        else if (tool==TOOL_ERASOR && e.getButton()==MouseEvent.BUTTON1) // initiate a drag-rect-delete sequence
01563        {
01564            highlightAtom=highlightBond=0;
01565            ResetSelected(true);
01566            toolDragReason=DRAG_SELECT;
01567            toolDragX1=toolDragX2=e.getX();
01568            toolDragY1=toolDragY2=e.getY();
01569            repaint();
01570        }
01571        else if (tool==TOOL_ATOM) // note drag or change atom
01572        {
01573            toolBondFrom=PickAtom(e.getX(),e.getY());           // in case it gets...
01574            toolAtomSnap=e.getButton()==MouseEvent.BUTTON1;     // ... dragged later
01575        }
01576        else if (tool==TOOL_BOND && (e.getButton()==MouseEvent.BUTTON1 || e.getButton()==MouseEvent.BUTTON3)) // initiate bond drag
01577        {
01578            highlightAtom=highlightBond=0;
01579            toolBondDrag=false;
01580            toolBondFrom=PickAtom(e.getX(),e.getY());
01581            toolSnap=e.getButton()==MouseEvent.BUTTON1;
01582            if (toolBondFrom>0)
01583            {
01584               toolBondToX=mol.AtomX(toolBondFrom);
01585               toolBondToY=mol.AtomY(toolBondFrom);
01586               repaint();
01587            }
01588            toolBondFromX=XToAng(e.getX()); 
01589            toolBondFromY=YToAng(e.getY());
01590            toolBondHit=PickBond(e.getX(),e.getY());
01591        }
01592        else if (tool==TOOL_TEMPLATE && e.getButton()==MouseEvent.BUTTON1) // slap a template right down
01593        {
01594            boolean swap=false;
01595            if (highlightAtom!=0 && templateIdx>0) AdjustTemplateByAtom(highlightAtom);
01596            else if (highlightBond!=0 && templateIdx<0) swap=AdjustTemplateByBond(highlightBond);
01597            else AdjustTemplateByCoord(XToAng(trackX),YToAng(trackY));
01598               
01599            CacheUndo();
01600               
01601            if (templateIdx>=0) 
01602               TemplateSetByAtom(highlightAtom); 
01603               else 
01604               TemplateSetByBond(highlightBond,swap);
01605        }
01606        else if (tool==TOOL_ROTATOR && (e.getButton()==MouseEvent.BUTTON1 || e.getButton()==MouseEvent.BUTTON3) && AnySelected())
01607        { // initiate a rotation-drag
01608            toolDragReason=DRAG_ROTATE;
01609            toolSnap=e.getButton()==MouseEvent.BUTTON1;
01610            if (highlightAtom>0) {toolDragX1=AngToX(mol.AtomX(highlightAtom)); toolDragY1=AngToY(mol.AtomY(highlightAtom));}
01611            else if (highlightBond>0) 
01612            {
01613               toolDragX1=AngToX(0.5*(mol.AtomX(mol.BondFrom(highlightBond))+mol.AtomX(mol.BondTo(highlightBond))));
01614               toolDragY1=AngToY(0.5*(mol.AtomY(mol.BondFrom(highlightBond))+mol.AtomY(mol.BondTo(highlightBond))));
01615            }
01616            else {toolDragX1=e.getX(); toolDragY1=e.getY();}
01617            highlightAtom=highlightBond=0;
01618            
01619            toolDragX2=toolDragX1;
01620            toolDragY2=toolDragY1;
01621            repaint();
01622        }
01623        else if (tool==TOOL_CHARGE && highlightAtom>0) // offset charge
01624        {
01625            int chg=mol.AtomCharge(highlightAtom);
01626            if (e.getButton()==MouseEvent.BUTTON1) chg+=toolCharge;
01627            else if (e.getButton()==MouseEvent.BUTTON3) chg-=toolCharge;
01628            else chg=0;
01629            CacheUndo();
01630            mol.SetAtomCharge(highlightAtom,chg);
01631            repaint();
01632        }
01633        
01634        CheckDirtiness();
01635     }
01636     
01637     public void mouseReleased(MouseEvent e)
01638     {
01639        if ((tool==TOOL_CURSOR && toolDragReason!=0) || (tool==TOOL_ROTATOR && toolDragReason==DRAG_SELECT) && editable)
01640        { // solidify a translate or select
01641            toolDragX2=e.getX();
01642            toolDragY2=e.getY();
01643            double mx=toolDragX2-toolDragX1,my=toolDragY2-toolDragY1;
01644            
01645            if (toolDragReason==DRAG_SELECT && dragged!=null)
01646            {
01647               for (int n=0;n<mol.NumAtoms();n++) selected[n]=selected[n] || dragged[n];
01648            }
01649            if (toolDragReason==DRAG_MOVE && selected!=null && mx*mx+my*my>5*5)
01650            {
01651               double dx=mx/scale,dy=-my/scale;
01652               CacheUndo();
01653               for (int n=1;n<=mol.NumAtoms();n++) if (selected[n-1]) 
01654               {
01655                   mol.SetAtomPos(n,mol.AtomX(n)+dx,mol.AtomY(n)+dy);
01656               }
01657               ClearTemporary(false);
01658               DetermineSize();
01659            }
01660             if (toolDragReason==DRAG_COPY && selected!=null && mx*mx+my*my>5*5)
01661            {
01662               double dx=(toolDragX2-toolDragX1)/scale,dy=-(toolDragY2-toolDragY1)/scale;
01663               int oldNumAtoms=mol.NumAtoms(),oldNumBonds=mol.NumBonds();
01664               int[] newPos=new int[mol.NumAtoms()];
01665               CacheUndo();
01666               for (int n=1;n<=oldNumAtoms;n++) if (selected[n-1]) 
01667               {
01668                   newPos[n-1]=mol.AddAtom(mol.AtomElement(n),mol.AtomX(n)+dx,mol.AtomY(n)+dy,mol.AtomCharge(n),mol.AtomUnpaired(n));
01669               }
01670               for (int n=1;n<=oldNumBonds;n++) if (selected[mol.BondFrom(n)-1] && selected[mol.BondTo(n)-1])
01671               {
01672                   mol.AddBond(newPos[mol.BondFrom(n)-1],newPos[mol.BondTo(n)-1],mol.BondOrder(n),mol.BondType(n));
01673               }
01674 
01675               ClearTemporary();
01676               selected=new boolean[mol.NumAtoms()];
01677               for (int n=1;n<=mol.NumAtoms();n++) selected[n-1]=n>oldNumAtoms;
01678               DetermineSize();
01679            }
01680            if (toolDragReason==DRAG_SCALE && selected!=null && mx*mx+my*my>5*5)
01681            {
01682               double extmul=DragExtendBy(mx,my);
01683               double cx=0,cy=0;
01684               int count=0;
01685               for (int n=1;n<=mol.NumAtoms();n++) if (selected[n-1]) {cx+=mol.AtomX(n); cy+=mol.AtomY(n); count++;}
01686               cx/=count; cy/=count;
01687               CacheUndo();
01688               for (int n=1;n<=mol.NumAtoms();n++) if (selected[n-1]) 
01689               {
01690                   mol.SetAtomPos(n,(mol.AtomX(n)-cx)*extmul+cx,(mol.AtomY(n)-cy)*extmul+cy);
01691               }
01692               
01693               ClearTemporary(false);
01694               DetermineSize();
01695            }
01696 
01697            toolDragReason=0;
01698            dragged=null;
01699            repaint();
01700        }
01701        if (tool==TOOL_ERASOR && toolDragReason!=0) // erase selection
01702        {
01703            toolDragX2=e.getX();
01704            toolDragY2=e.getY();
01705            if (toolDragReason==DRAG_SELECT && dragged!=null)
01706            {
01707               for (int n=0;n<mol.NumAtoms();n++) selected[n]=selected[n] || dragged[n];
01708               DeleteSelected();
01709               ClearTemporary();
01710            }
01711            toolDragReason=0;
01712            dragged=null;
01713            repaint();
01714        }
01715        else if (tool==TOOL_ROTATOR && toolDragReason==DRAG_ROTATE) // solidify a rotation
01716        {
01717            double dx=toolDragX2-toolDragX1,dy=toolDragY2-toolDragY1;
01718            double th=-Math.atan2(dy,dx)*180/Math.PI;
01719            if (toolSnap) th=Math.round(th/15)*15;
01720            if (Math.abs(th)>1) 
01721            {
01722               CacheUndo();
01723               th=th*Math.PI/180;
01724               double ax=XToAng(toolDragX1),ay=YToAng(toolDragY1);
01725               for (int n=1;n<=mol.NumAtoms();n++) if (selected[n-1])
01726               {
01727                   double rx=mol.AtomX(n)-ax,ry=mol.AtomY(n)-ay;
01728                   double rth=Math.atan2(ry,rx),ext=Math.sqrt(rx*rx+ry*ry);
01729                   mol.SetAtomPos(n,ax+ext*Math.cos(rth+th),ay+ext*Math.sin(rth+th));
01730               }
01731               ClearTemporary(false);
01732               DetermineSize();
01733            }
01734 
01735            toolDragReason=0;
01736            dragged=null;
01737            repaint();
01738        }
01739        else if (tool==TOOL_ATOM && toolAtomDrag && toolBondFrom>0) // place a new atom-from
01740        {
01741            CacheUndo();
01742            mol.AddAtom(toolAtomType,toolBondToX,toolBondToY);
01743            mol.AddBond(toolBondFrom,mol.NumAtoms(),1);
01744            ClearTemporary();
01745            DetermineSize();
01746            toolAtomDrag=false;
01747            toolBondFrom=0;
01748            repaint();
01749        }
01750        else if (tool==TOOL_BOND) // bond addition, possibly by adding new atoms, too
01751        {
01752            toolBondToX=XToAng(e.getX());
01753            toolBondToY=YToAng(e.getY());
01754 
01755            int joinTo=PickAtom(e.getX(),e.getY());
01756            if (toolBondFrom>0 && joinTo==0 && toolSnap)
01757            {
01758               SnapToolBond();
01759               joinTo=PickAtom((int)AngToX(toolBondToX),(int)AngToY(toolBondToY));
01760            }
01761            
01762            if (e.getButton()==MouseEvent.BUTTON1 && toolBondFrom==0 && toolBondHit>0) // change hit bond order
01763            {
01764               int i=PickBond(e.getX(),e.getY());
01765               if (i==toolBondHit)
01766               {
01767                   CacheUndo();
01768                   if (toolBondOrder==mol.BondOrder(i) && toolBondType==mol.BondType(i)) 
01769                      mol.SetBondFromTo(i,mol.BondTo(i),mol.BondFrom(i));
01770                   mol.SetBondOrder(i,toolBondOrder); 
01771                   mol.SetBondType(i,toolBondType);
01772                   ClearTemporary();
01773               }
01774            }
01775            else if (toolBondFrom==0) // create a new bond from/in the middle of nowhere, possibly connected to something
01776            {
01777               int a1=0,a2=0;
01778               double x1=0,x2=0,y1=0,y2=0;
01779               if (toolBondDrag)
01780               {
01781                   if (toolSnap) SnapToolBond();
01782                   x1=toolBondFromX;
01783                   y1=toolBondFromY;
01784                   a2=PickAtom(e.getX(),e.getY());
01785                   if (a2>0) {x2=mol.AtomX(a2); y2=mol.AtomY(a2);} else {x2=toolBondToX; y2=toolBondToY;}
01786               }
01787               else
01788               {
01789                   x1=x2=XToAng(e.getX()); if ((e.getModifiers()&MouseEvent.SHIFT_MASK)>0) {x1-=0.5*IDEALBOND; x2+=0.5*IDEALBOND;}
01790                   y1=y2=YToAng(e.getY()); if ((e.getModifiers()&MouseEvent.SHIFT_MASK)==0) {y1-=0.5*IDEALBOND; y2+=0.5*IDEALBOND;}
01791               }
01792               double dx=x2-x1,dy=y2-y1;
01793               if (dx*dx+dy*dy>0.5*0.5)
01794               {
01795                   CacheUndo();
01796                   a1=mol.AddAtom("C",x1,y1,0,0);
01797                   if (a2==0) a2=mol.AddAtom("C",x2,y2,0,0);
01798                   mol.AddBond(a1,a2,toolBondOrder);
01799                   ClearTemporary();
01800               }
01801               repaint();
01802            }
01803            else if (joinTo>0 && joinTo!=toolBondFrom) // link two atoms together
01804            {
01805               CacheUndo();
01806               mol.AddBond(toolBondFrom,joinTo,toolBondOrder);
01807               mol.SetBondType(mol.NumBonds(),toolBondType);
01808               ClearTemporary();
01809            }
01810            else if (toolBondFrom>0) // draw a new bond out to some place not specified by the user, i.e. a healthy guess
01811            {
01812               double dx=toolBondToX-mol.AtomX(toolBondFrom),dy=toolBondToY-mol.AtomY(toolBondFrom);
01813               if (toolBondFrom==joinTo) 
01814               {
01815                   int adj[]=mol.AtomAdjList(toolBondFrom);
01816                   ArrayList<Double> poss=new ArrayList<Double>();
01817                   double ax=mol.AtomX(toolBondFrom),ay=mol.AtomY(toolBondFrom);
01818                   if (adj.length==0) poss.add(0.0);
01819                   else if (adj.length==1)
01820                   {
01821                      double ang=Math.atan2(mol.AtomY(adj[0])-ay,mol.AtomX(adj[0])-ax)*180/Math.PI;
01822                      if (toolBondOrder!=3)
01823                      {
01824                          poss.add(ang+120);
01825                          poss.add(ang-120);
01826                      }
01827                      else poss.add(ang+180);
01828                   }
01829                   else if (adj.length==2)
01830                   {
01831                      double ang1=Math.atan2(mol.AtomY(adj[0])-ay,mol.AtomX(adj[0])-ax)*180/Math.PI;
01832                      double ang2=Math.atan2(mol.AtomY(adj[1])-ay,mol.AtomX(adj[1])-ax)*180/Math.PI;
01833                      if (ang2<ang1) ang2+=360;
01834                      if (ang2-ang1<180) poss.add(0.5*(ang1+ang2)+180); else poss.add(0.5*(ang1+ang2));
01835                   }
01836                   else for (int n=0;n<adj.length;n++)
01837                   {
01838                      double ang=Math.atan2(mol.AtomY(adj[n])-ay,mol.AtomX(adj[n])-ax)*180/Math.PI;
01839                      poss.add(ang+180);
01840                   }
01841                   double ang=poss.get(0);
01842                   if (poss.size()>1)
01843                   {
01844                      int best=-1;
01845                      double bestScore=0;
01846                      for (int n=0;n<poss.size();n++)
01847                      {
01848                          double nx=ax+IDEALBOND*Math.cos(poss.get(n)*Math.PI/180);
01849                          double ny=ay+IDEALBOND*Math.sin(poss.get(n)*Math.PI/180);
01850                          double score=0;
01851                          for (int i=1;i<=mol.NumAtoms();i++)
01852                          {
01853                             dx=mol.AtomX(i)-nx;
01854                             dy=mol.AtomY(i)-ny;
01855                             score+=1/Math.min(1000,dx*dx+dy*dy);
01856                          }
01857                          if (best<0 || score<bestScore) {best=n; bestScore=score;}
01858                      }
01859                      ang=poss.get(best);
01860                   }
01861                   
01862                   dx=IDEALBOND*Math.cos(ang*Math.PI/180);
01863                   dy=IDEALBOND*Math.sin(ang*Math.PI/180);
01864                   toolBondToX=ax+dx;
01865                   toolBondToY=ay+dy;
01866               }
01867               if (dx*dx+dy*dy>0.5)
01868               {
01869                   CacheUndo();
01870                   mol.AddAtom("C",toolBondToX,toolBondToY);
01871                   mol.AddBond(toolBondFrom,mol.NumAtoms(),toolBondOrder);
01872                   mol.SetBondType(mol.NumBonds(),toolBondType);
01873                   ClearTemporary();
01874                   DetermineSize();
01875               }
01876            }
01877 
01878            toolBondDrag=false;
01879            toolBondFrom=0;
01880            toolBondHit=0;
01881            repaint();
01882        }
01883 
01884        CheckDirtiness();
01885     }
01886     
01887     public void mouseMoved(MouseEvent e)
01888     {
01889        boolean redraw=false;
01890        
01891        if (trackX!=e.getX() || trackY!=e.getY())
01892        {
01893            if (tool==TOOL_TEMPLATE) redraw=true;
01894        }
01895        
01896        trackX=e.getX();
01897        trackY=e.getY();
01898 
01899        if (e.getButton()==0)
01900        {
01901            int mx=e.getX(),my=e.getY();
01902            int newAtom=0,newBond=0;
01903 
01904            newAtom=PickAtom(mx,my);
01905            if (newAtom==0) newBond=PickBond(mx,my);
01906            
01907            if (tool==TOOL_TEMPLATE && templateIdx>0) newBond=0;
01908            if (tool==TOOL_TEMPLATE && templateIdx<0) newAtom=0;
01909            
01910            if (newAtom!=highlightAtom || newBond!=highlightBond)
01911            {
01912               highlightAtom=newAtom;
01913               highlightBond=newBond;
01914               redraw=true;
01915            }
01916        }
01917        
01918        if (redraw) repaint();
01919     }
01920     
01921     public void mouseDragged(MouseEvent e)
01922     {
01923        boolean redraw=false;
01924        if (tool==TOOL_TEMPLATE && (trackX!=e.getX() || trackY!=e.getY())) redraw=true;
01925        trackX=e.getX(); trackY=e.getY();
01926     
01927        if ((tool==TOOL_CURSOR && toolDragReason!=0) || (tool==TOOL_ERASOR && toolDragReason!=0) ||
01928            (tool==TOOL_ROTATOR && toolDragReason==DRAG_SELECT))
01929        {
01930            toolDragX2=e.getX();
01931            toolDragY2=e.getY();
01932            if (toolDragReason==DRAG_SELECT)
01933            {
01934               int x=(int)toolDragX1,y=(int)toolDragY1,w=(int)toolDragX2-x,h=(int)toolDragY2-y;
01935               if (w<0) {w=-w; x-=w;}
01936               if (h<0) {h=-h; y-=h;}
01937               dragged=new boolean[mol.NumAtoms()];
01938               for (int n=0;n<mol.NumAtoms();n++) dragged[n]=px[n]>=x && px[n]<=x+w && py[n]>=y && py[n]<=y+h;
01939            }
01940            redraw=true;
01941        }
01942        else if (tool==TOOL_ROTATOR && toolDragReason==DRAG_ROTATE)
01943        {
01944            toolDragX2=e.getX();
01945            toolDragY2=e.getY();
01946            redraw=true;
01947        }
01948        else if (tool==TOOL_ATOM && toolBondFrom!=0)
01949        {
01950            if (!toolAtomDrag)
01951            {
01952               double dx=XToAng(e.getX())-mol.AtomX(toolBondFrom),dy=YToAng(e.getY())-mol.AtomY(toolBondFrom);
01953               if (dx*dx+dy*dy>0.8*0.8) 
01954               {
01955                   toolAtomDrag=true;
01956                   toolBondOrder=1;
01957                   toolBondType=Molecule.BONDTYPE_NORMAL;
01958               }
01959            }
01960            if (toolAtomDrag)
01961            {
01962               toolBondToX=XToAng(e.getX());
01963               toolBondToY=YToAng(e.getY());
01964               if (toolAtomSnap) SnapToolBond();
01965               redraw=true;
01966            }
01967        }
01968        else if (tool==TOOL_BOND /*&& toolBondFrom!=0*/)
01969        {
01970            toolBondToX=XToAng(e.getX());
01971            toolBondToY=YToAng(e.getY());
01972            int joinTo=PickAtom(e.getX(),e.getY());
01973            if (!toolBondDrag)
01974               if (Math.abs(toolBondToX-toolBondFromX)>2/scale || Math.abs(toolBondToY-toolBondFromY)>2/scale) toolBondDrag=true;
01975            if (joinTo>0) {toolBondToX=mol.AtomX(joinTo); toolBondToY=mol.AtomY(joinTo);}
01976            else if (toolSnap) SnapToolBond();
01977            redraw=true;
01978        }
01979 
01980        if (redraw) repaint();
01981        CheckDirtiness();
01982     }
01983     
01984     public void mouseWheelMoved(MouseWheelEvent e)
01985     {
01986        if (tool==TOOL_TEMPLATE)
01987        {
01988            double cx=0,cy=0;
01989            for (int n=1;n<=template.NumAtoms();n++) {cx+=template.AtomX(n); cy+=template.AtomY(n);}
01990            cx/=template.NumAtoms();
01991            cy/=template.NumAtoms();
01992        
01993            double accel=e.isShiftDown() ? 3 : 1;
01994        
01995            if (e.isControlDown()) // scale
01996            {
01997               double factor=1-0.1*accel*e.getWheelRotation();
01998               for (int n=1;n<=template.NumAtoms();n++) 
01999                   template.SetAtomPos(n,cx+(template.AtomX(n)-cx)*factor,cy+(template.AtomY(n)-cy)*factor);
02000            }
02001            else // rotate
02002            {
02003               double radians=5*accel*Math.PI/180*e.getWheelRotation();
02004               for (int n=1;n<=template.NumAtoms();n++) 
02005               {
02006                   double dx=template.AtomX(n)-cx,dy=template.AtomY(n)-cy;
02007                   double dist=Math.sqrt(dx*dx+dy*dy),theta=Math.atan2(dy,dx);
02008                   template.SetAtomPos(n,cx+dist*Math.cos(theta+radians),cy+dist*Math.sin(theta+radians));
02009               }
02010            }
02011            templDraw=template.Clone();
02012            repaint();
02013        }
02014     }
02015     
02016     // Other callbacks...
02017     
02018     public void focusGained(FocusEvent e) {}
02019     public void focusLost(FocusEvent e)
02020     {
02021        if (e.getSource()==toolAtomEditBox) CompleteAtomEdit();
02022     }
02023     public void keyPressed(KeyEvent e) {}
02024     public void keyReleased(KeyEvent e) {}
02025     public void keyTyped(KeyEvent e) 
02026     {
02027        if (e.getSource()==toolAtomEditBox) 
02028        {
02029            if (e.getKeyChar()=='\n') CompleteAtomEdit();
02030        }
02031     }
02032 
02033     public void componentHidden(ComponentEvent e) {}
02034     public void componentMoved(ComponentEvent e) {}
02035     public void componentResized(ComponentEvent e)
02036     {
02037        if (autoScale) {ScaleToFit(); repaint();}
02038     }
02039     public void componentShown(ComponentEvent e) 
02040     {
02041        if (autoScale) {ScaleToFit(); repaint();}
02042     }
02043 }
02044 
02045 
02046 
02047 
02048 
02049 
02050 
02051 
02052 
02053 
02054 
02055