Back to index

wims  3.65+svn20090927
DrawMolecule.java
Go to the documentation of this file.
00001 /*
00002     WIMSchem Elements: Chemistry molecular diagram drawing tool.
00003     
00004     (c) 2007 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 
00018 /*
00019     A class dedicated to the drawing of a molecule. Lifecycle: provide the minimal input data, add any additional parameters as necessary,
00020     draw onto a canvas of some kind, possibly grab back some data, then destroy.
00021 */
00022 
00023 public class DrawMolecule
00024 {
00025     Molecule mol;
00026     Graphics2D g;
00027 
00028     Color backgr=Color.WHITE;
00029     Color colHighlight=null,colSelected=null,colDragged=null;
00030     
00031     boolean showHydr=false;
00032     int showMode=SHOW_ALL_ELEMENTS;
00033     boolean showSter=false;
00034     
00035     double offsetX=0,offsetY=0; // in Angstrom units
00036     double scale=DEFSCALE; // pixels per Angstrom
00037 
00038     String[] atomLabel=null;
00039     boolean[] expl=null;
00040     int[] hcount=null;
00041     double[] px=null,py=null,rw=null,rh=null;
00042     double[][] bfx=null,bfy=null,btx=null,bty=null;
00043     int[] hdir=null;
00044 
00045     Font font=null,smallFont=null,boldFont=null;
00046     FontMetrics metrics=null,smallMetrics=null,boldMetrics=null;
00047     int txh=0;
00048     
00049     boolean[] selected=null,dragged=null;
00050     int highlightAtom=0,highlightBond=0;
00051 
00052     boolean bondInProgress=false;
00053     int bipFrom,bipOrder,bipType;
00054     double bipToX,bipToY;
00055     
00056     boolean atomInProgress=false;
00057     String aipLabel;
00058     double aipToX,aipToY;
00059     
00060     boolean newBondLine=false;
00061     double nblX1,nblY1,nblX2,nblY2;
00062 
00063     boolean dragSelect=false;
00064     int dslX1,dslY1,dslX2,dslY2;
00065     
00066     boolean dragScale=false;
00067     double dscCX,dscCY,dscExtMul;
00068 
00069     boolean dragMove=false;
00070     double dmvDX,dmvDY;
00071     boolean dmvCopy;
00072     
00073     boolean dragRotate=false;
00074     double droTheta;
00075     int droX,droY;
00076     
00077     boolean outlineTemplate=false;
00078     Molecule oltMol;
00079     // jm.evers 27/12/2008   
00080     boolean viewC=MainPanel.viewC;
00081     //
00082     // public constants
00083     public static final double DEFSCALE=20; // default # Angstroms per pixel
00084     public static final int SHOW_ELEMENTS=0;
00085     public static final int SHOW_ALL_ELEMENTS=1;
00086     public static final int SHOW_INDEXES=2;
00087     public static final int SHOW_RINGID=3;
00088     public static final int SHOW_PRIORITY=4;
00089     public static final int SHOW_MAPNUM=5;
00090 
00091     // constructor
00092 
00093     public DrawMolecule(Molecule Mol,Graphics2D Gr)
00094     {
00095        mol=Mol;
00096        g=Gr;
00097     }
00098     
00099     // methods to provide other drawing options...
00100     
00101     public void SetBackground(Color Col) {backgr=Col;}
00102     public void SetShowHydr(boolean ShowH) {showHydr=ShowH;}
00103     public void SetShowMode(int Mode) {showMode=Mode;}
00104     public void SetShowStereo(boolean ShowSter) {showSter=ShowSter;}
00105     public void SetTransform(double OX,double OY,double Scale) {offsetX=OX; offsetY=OY; scale=Scale;}
00106     public void SetSelected(boolean[] Selected,boolean[] Dragged) {selected=Selected; dragged=Dragged;}
00107     public void SetHighlight(int Atom,int Bond) {highlightAtom=Atom; highlightBond=Bond;}
00108 
00109     public void BondInProgress(int From,double X,double Y,int Order,int Type)
00110     {
00111        bondInProgress=true;
00112        bipFrom=From;
00113        bipToX=X;
00114        bipToY=Y;
00115        bipOrder=Order;
00116        bipType=Type;
00117     }
00118     public void AtomInProgress(String Label,double X,double Y)
00119     {
00120        atomInProgress=true;
00121        aipLabel=Label;
00122        aipToX=X;
00123        aipToY=Y;
00124     }
00125     public void NewBondLine(double X1,double Y1,double X2,double Y2)
00126     {
00127        // !! bond order and type should be accounted for...
00128        newBondLine=true;
00129        nblX1=X1;
00130        nblY1=Y1;
00131        nblX2=X2;
00132        nblY2=Y2;
00133     }
00134     public void DragSelect(int X1,int Y1,int X2,int Y2)
00135     {
00136        dragSelect=true;
00137        dslX1=X1;
00138        dslY1=Y1;
00139        dslX2=X2;
00140        dslY2=Y2;
00141     }
00142     public void DragScale(double CX,double CY,double ExtMul)
00143     {
00144        dragScale=true;
00145        dscCX=CX;
00146        dscCY=CY;
00147        dscExtMul=ExtMul;
00148     }
00149     public void DragMove(double DX,double DY,boolean Copy)
00150     {
00151        dragMove=true;
00152        dmvDX=DX;
00153        dmvDY=DY;
00154        dmvCopy=Copy;
00155     }
00156     public void DragRotate(double Theta,int X,int Y)
00157     {
00158        dragRotate=true;
00159        droTheta=Theta;
00160        droX=X;
00161        droY=Y;
00162     }
00163     public void OutlineTemplate(Molecule Templ)
00164     {
00165        outlineTemplate=true;
00166        oltMol=Templ;
00167     }
00168     
00169     // methods to extract calculated properties for future reference
00170     
00171     public double[] GetPX() {return px;}
00172     public double[] GetPY() {return py;}
00173     public double[] GetRW() {return rw;}
00174     public double[] GetRH() {return rh;}
00175     public double[][] GetBFX() {return bfx;}
00176     public double[][] GetBFY() {return bfy;}
00177     public double[][] GetBTX() {return btx;}
00178     public double[][] GetBTY() {return bty;}
00179     
00180     // perform the actual drawing
00181     
00182     public void Draw()
00183     {
00184        SetupColours();
00185        SetupFonts();
00186        SetupLabels();
00187        SetupPositions();
00188        SetupHydrogens();
00189        
00190        DrawBacklighting();
00191        DrawBonds();
00192        DrawAtoms();
00193        DrawEffects();
00194        DrawAnnotations();
00195        DrawCorrections();
00196     }
00197     
00198     // private methods
00199     
00200     // decides on particular colors
00201     void SetupColours()
00202     {
00203        if (colHighlight==null) colHighlight=backgr.darker();
00204        if (colSelected==null) colSelected=new Color(colHighlight.getRed(),colHighlight.getGreen(),255);
00205        if (colDragged==null) colDragged=new Color(colHighlight.getRed(),192,255);
00206     }
00207 
00208     // creates the fonts and metrics
00209     void SetupFonts()
00210     {
00211     
00212        font=new Font("SansSerif",Font.PLAIN,(int)(0.7*scale));
00213        g.setFont(font);
00214        metrics=g.getFontMetrics();
00215        txh=(int)(metrics.getAscent()*0.85);
00216        
00217        smallFont=new Font("SansSerif",Font.PLAIN,(int)(0.35*scale));
00218        g.setFont(smallFont);
00219        smallMetrics=g.getFontMetrics();
00220        
00221        boldFont=new Font("SansSerif",Font.BOLD,(int)(0.7*scale));
00222        g.setFont(boldFont);
00223        boldMetrics=g.getFontMetrics();
00224        font=new Font("SansSerif",Font.PLAIN,(int)(0.7*scale));
00225 
00226 
00227 
00228     }
00229     
00230     // decides what exactly needs to be drawn for each atom label
00231     void SetupLabels(){
00232        if (selected==null) selected=new boolean[mol.NumAtoms()];
00233        if (dragged==null) dragged=new boolean[mol.NumAtoms()];
00234     
00235        if (atomLabel!=null && expl!=null && hcount!=null) return;
00236     
00237        atomLabel=new String[mol.NumAtoms()];
00238        expl=new boolean[mol.NumAtoms()];
00239        hcount=new int[mol.NumAtoms()];
00240        for (int n=1;n<=mol.NumAtoms();n++){
00241            // jm.evers: to accomodate param elements="no" ... only for Carbon 
00242            // all other elements will still be shown, ofcourse
00243            if( mol.AtomElement(n).equals("C") && viewC == false  && (showMode==SHOW_ELEMENTS || showMode==SHOW_ALL_ELEMENTS  )) atomLabel[n-1]=mol.AtomMapNum(n)>0 ? String.valueOf(mol.AtomMapNum(n)) : "";
00244            else if (showMode==SHOW_ELEMENTS) atomLabel[n-1]=mol.AtomExplicit(n) ? mol.AtomElement(n) : "";
00245            else if (showMode==SHOW_ALL_ELEMENTS) atomLabel[n-1]=mol.AtomElement(n);
00246            else if (showMode==SHOW_INDEXES) atomLabel[n-1]=String.valueOf(n);
00247            else if (showMode==SHOW_RINGID) atomLabel[n-1]=String.valueOf(mol.AtomRingBlock(n));
00248            else if (showMode==SHOW_PRIORITY) atomLabel[n-1]=String.valueOf(mol.AtomPriority(n));
00249            else if (showMode==SHOW_MAPNUM) atomLabel[n-1]=mol.AtomMapNum(n)>0 ? String.valueOf(mol.AtomMapNum(n)) : "";
00250            else atomLabel[n-1]="?";
00251            expl[n-1]=atomLabel[n-1].length()>0;
00252            hcount[n-1]=showHydr && (showMode==SHOW_ELEMENTS || showMode==SHOW_ALL_ELEMENTS) && expl[n-1] ? mol.AtomHydrogens(n) : 0;
00253        }
00254     }
00255     
00256     // configure the positions of all of the atoms, and their associated bonds; apply whatever aesthetic corrections are deemed necessary
00257     void SetupPositions()
00258     {
00259        if (px!=null && py!=null && rw!=null && rh!=null && bfx!=null && bfy!=null && btx!=null && bty!=null) return;
00260        
00261        px=new double[mol.NumAtoms()];
00262        py=new double[mol.NumAtoms()];
00263        rw=new double[mol.NumAtoms()];
00264        rh=new double[mol.NumAtoms()];
00265        bfx=new double[3][mol.NumBonds()+1];
00266        bfy=new double[3][mol.NumBonds()+1];
00267        btx=new double[3][mol.NumBonds()+1];
00268        bty=new double[3][mol.NumBonds()+1];
00269 
00270        for (int n=1;n<=mol.NumAtoms();n++)
00271        {
00272            px[n-1]=AngToX(mol.AtomX(n));
00273            py[n-1]=AngToY(mol.AtomY(n));
00274            if (expl[n-1])
00275            {
00276               if (mol.AtomMapNum(n)==0)
00277                   rw[n-1]=0.5*metrics.stringWidth(atomLabel[n-1]);
00278               else
00279                   rw[n-1]=0.5*boldMetrics.stringWidth(atomLabel[n-1]);
00280               rh[n-1]=0.5*(metrics.getAscent()+metrics.getDescent());
00281            }
00282            else
00283            {
00284               rw[n-1]=rh[n-1]=txh*0.4;
00285            }
00286        }
00287        
00288        // figure out which double bonds should go on one particular side
00289        int bondside[]=new int[mol.NumBonds()];
00290        for (int n=0;n<mol.NumBonds();n++) bondside[n]=0;
00291        int ring6[][]=mol.FindRingSize(6),nring6=ring6.length;
00292        for (int n=0;n<ring6.length;n++) // convert to bond indexes
00293        {
00294            int a=ring6[n][0];
00295            for (int i=0;i<6;i++) ring6[n][i]=mol.FindBond(ring6[n][i],i==5 ? a : ring6[n][i+1]);
00296        }
00297        int r6score[]=new int[ring6.length];
00298        while (true)
00299        {
00300            int best=-1;
00301            for (int n=0;n<nring6;n++)
00302            {
00303               r6score[n]=0;
00304               for (int i=0;i<6;i++) if (mol.BondOrder(ring6[n][i])==2 && bondside[ring6[n][i]-1]==0) r6score[n]++;
00305               if (r6score[n]>0 && (best<0 || r6score[n]>r6score[best])) best=n;
00306            }
00307            if (best<0) break;
00308            for (int n=0;n<6;n++)
00309            {
00310               int bond=ring6[best][n];
00311               if (mol.BondOrder(bond)!=2 || bondside[bond-1]!=0) continue;
00312               int from=mol.BondFrom(bond),to=mol.BondTo(bond);
00313               int numLeft=0,numRight=0,numFrom=0,numTo=0;
00314               double thbond=Math.atan2(mol.AtomY(to)-mol.AtomY(from),mol.AtomX(to)-mol.AtomX(from));
00315               for (int i=0;i<6;i++) if (i!=n)
00316               {
00317                   int o=mol.BondOther(ring6[best][i],from);
00318                   double theta;
00319                   if (o>0)
00320                   {
00321                      theta=Math.atan2(mol.AtomY(o)-mol.AtomY(from),mol.AtomX(o)-mol.AtomX(from));
00322                      numFrom++;
00323                   }
00324                   else
00325                   {
00326                      o=mol.BondOther(ring6[best][i],to);
00327                      if (o>0)
00328                      {
00329                          theta=Math.atan2(mol.AtomY(o)-mol.AtomY(to),mol.AtomX(o)-mol.AtomX(to));
00330                          numTo++;
00331                      }
00332                      else continue;
00333                   }
00334                   theta=theta-thbond;
00335                   theta+=theta>Math.PI ? -2*Math.PI : theta<-Math.PI ? 2*Math.PI : 0;
00336                   if (theta<0) numLeft++; 
00337                   if (theta>0) numRight++;
00338               }
00339               if (numFrom>0 && numTo>0) bondside[bond-1]=numLeft>numRight ? -1 : 1;
00340            }
00341            if (best<nring6-1) ring6[best]=ring6[--nring6];
00342        }
00343        // remaining bonds, if they are in a ring or are unevenly balanced
00344        for (int n=1;n<=mol.NumBonds();n++) if (mol.BondOrder(n)==2 && bondside[n-1]==0)
00345        {
00346            int from=mol.BondFrom(n),to=mol.BondTo(n);
00347            int[] adj1=mol.AtomAdjList(from),adj2=mol.AtomAdjList(to);
00348            if (adj1.length>=3 && adj2.length>=3 && !mol.BondInRing(n)) continue; // evenly balanced chain bond, leave in middle
00349            if ((adj1.length==1 && adj2.length>=3) || (adj2.length==1 && adj1.length>=3)) continue; // ketone-like bond
00350            int numLeft=0,numRight=0,numFrom=0,numTo=0;
00351            double thbond=Math.atan2(mol.AtomY(to)-mol.AtomY(from),mol.AtomX(to)-mol.AtomX(from));
00352            for (int i=0;i<adj1.length;i++) if (adj1[i]!=to)
00353            {
00354               double theta=Math.atan2(mol.AtomY(adj1[i])-mol.AtomY(from),mol.AtomX(adj1[i])-mol.AtomX(from))-thbond;
00355               theta+=theta>Math.PI ? -2*Math.PI : theta<-Math.PI ? 2*Math.PI : 0;
00356               int v=mol.BondInRing(n) && mol.AtomRingBlock(from)==mol.AtomRingBlock(adj1[i]) ? 2 : 1;
00357               if (theta<0) numLeft+=v; 
00358               if (theta>0) numRight+=v;
00359            }
00360            for (int i=0;i<adj2.length;i++) if (adj2[i]!=from)
00361            {
00362               double theta=Math.atan2(mol.AtomY(adj2[i])-mol.AtomY(to),mol.AtomX(adj2[i])-mol.AtomX(to))-thbond;
00363               theta+=theta>Math.PI ? -2*Math.PI : theta<-Math.PI ? 2*Math.PI : 0;
00364               int v=mol.BondInRing(n) && mol.AtomRingBlock(to)==mol.AtomRingBlock(adj2[i]) ? 2 : 1;
00365               if (theta<0) numLeft+=v; 
00366               if (theta>0) numRight+=v;
00367            }
00368            if (numLeft!=numRight) bondside[n-1]=numLeft>numRight ? -1 : 1;
00369        }
00370        
00371        int extraAtom=bondInProgress ? 1 : 0;
00372        
00373        // define positions for the bonds
00374        for (int n=1;n<=mol.NumBonds() + extraAtom;n++)
00375        {
00376            int from,to,order,type,side;
00377            double x1,y1,x2,y2,w1,h1,w2,h2;
00378            boolean expl1,expl2;
00379            if (n<=mol.NumBonds())
00380            {
00381               from=mol.BondFrom(n); to=mol.BondTo(n); order=mol.BondOrder(n); type=mol.BondType(n); side=bondside[n-1];
00382               x1=px[from-1]; y1=py[from-1]; x2=px[to-1]; y2=py[to-1];
00383               w1=rw[from-1]; h1=rh[from-1]; w2=rw[to-1]; h2=rh[to-1];
00384               expl1=expl[from-1]; expl2=expl[to-1];
00385            }
00386            else // bond "in progress"
00387            {
00388               from=bipFrom; to=0; order=bipOrder; type=bipType; side=0;
00389               x1=px[from-1]; y1=py[from-1]; x2=AngToX(bipToX); y2=AngToY(bipToY);
00390               w1=rw[from-1]; h1=rh[from-1]; w2=0; h2=0;
00391               expl1=expl[from-1]; expl2=false;
00392            }
00393 
00394            double dx=x2-x1,dy=y2-y1;
00395            if (Math.abs(dx)<0.001)
00396            {
00397               dx=0;
00398               if (expl1) y1+=h1 * (dy>0 ? 1 : -1);
00399               if (expl2) y2+=h2 * (dy>0 ? -1 : 1);
00400            }
00401            else if (Math.abs(dy)<0.001) 
00402            {
00403               dy=0;
00404               if (expl1) x1+=w1 * (dx>0 ? 1 : -1);
00405               if (expl2) x2+=w2 * (dx>0 ? -1 : 1);
00406            }
00407            else
00408            {
00409               double xy=Math.abs(dx/dy),yx=Math.abs(dy/dx);
00410               if (expl1)
00411               {
00412                   x1+=(w1*h1*xy/(xy*h1+w1)) * (dx>0 ? 2 : -2);
00413                   y1+=(w1*h1*yx/(yx*h1+w1)) * (dy>0 ? 2 : -2);
00414               }
00415               if (expl2)
00416               {
00417                   x2+=(w2*h2*xy/(xy*h2+w2)) * (dx>0 ? -2 : 2);
00418                   y2+=(w2*h2*yx/(yx*h2+w2)) * (dy>0 ? -2 : 2);
00419               }
00420            }
00421            
00422            for (int i=0;i<3;i++) {bfx[i][n-1]=x1; bfy[i][n-1]=y1; btx[i][n-1]=x2; bty[i][n-1]=y2;}
00423            
00424            if (order==2 || order==3) 
00425            {
00426               double norm=0.15*scale/Math.sqrt(dx*dx+dy*dy);
00427               double ox=Math.signum(dy)*Math.ceil(norm*Math.abs(dy)*(order==3 ? 1 : 0.5));
00428               double oy=-Math.signum(dx)*Math.ceil(norm*Math.abs(dx)*(order==3 ? 1 : 0.5));
00429               if (order==2)
00430               {
00431                   bfx[0][n-1]+=ox*(side-1); bfy[0][n-1]+=oy*(side-1); btx[0][n-1]+=ox*(side-1); bty[0][n-1]+=oy*(side-1);
00432                   bfx[1][n-1]+=ox*(side+1); bfy[1][n-1]+=oy*(side+1); btx[1][n-1]+=ox*(side+1); bty[1][n-1]+=oy*(side+1);
00433                   
00434                   if (n<=mol.NumBonds() && side!=0 && mol.AtomRingBlock(from)>0 && mol.BondInRing(n))
00435                   {
00436                      int w=side<0 ? 0 : 1;
00437                      if (!expl1) {bfx[w][n-1]+=norm*dx; bfy[w][n-1]+=norm*dy;}
00438                      if (!expl2) {btx[w][n-1]-=norm*dx; bty[w][n-1]-=norm*dy;}
00439                   }
00440               }
00441               else
00442               {
00443                   bfx[1][n-1]-=ox; bfy[1][n-1]-=oy; btx[1][n-1]-=ox; bty[1][n-1]-=oy;
00444                   bfx[2][n-1]+=ox; bfy[2][n-1]+=oy; btx[2][n-1]+=ox; bty[2][n-1]+=oy;
00445               }
00446            }
00447        }
00448        // special case for non-ring non-explicit double bond endpoints... neighbouring single bonds get snapped "to"
00449        for (int n=1;n<=mol.NumAtoms();n++) if (atomLabel[n-1].length()==0 && mol.AtomRingBlock(n)==0)
00450        {
00451            boolean any=false;
00452            double dpx1=0,dpy1=0,dpx2=0,dpy2=0;
00453            for (int i=1;i<=mol.NumBonds();i++) if (mol.BondOrder(i)==2)
00454            {
00455               if (mol.BondFrom(i)==n) {dpx1=bfx[0][i-1]; dpy1=bfy[0][i-1]; dpx2=bfx[1][i-1]; dpy2=bfy[1][i-1]; any=true; break;}
00456               if (mol.BondTo(i)==n) {dpx1=btx[0][i-1]; dpy1=bty[0][i-1]; dpx2=btx[1][i-1]; dpy2=bty[1][i-1]; any=true; break;}
00457            }
00458            if (!any) continue;
00459            for (int i=1;i<=mol.NumBonds();i++) if (mol.BondOrder(i)==1)
00460            {
00461               if (mol.BondFrom(i)==n)
00462               {
00463                   double dx1=dpx1-btx[0][i-1],dy1=dpy1-bty[0][i-1];
00464                   double dx2=dpx2-btx[0][i-1],dy2=dpy2-bty[0][i-1];
00465                   if (dx1*dx1+dy1*dy1<dx2*dx2+dy2*dy2) {bfx[0][i-1]=dpx1; bfy[0][i-1]=dpy1;} else {bfx[0][i-1]=dpx2; bfy[0][i-1]=dpy2;}
00466               }
00467               else if (mol.BondTo(i)==n)
00468               {
00469                   double dx1=dpx1-bfx[0][i-1],dy1=dpy1-bfy[0][i-1];
00470                   double dx2=dpx2-bfx[0][i-1],dy2=dpy2-bfy[0][i-1];
00471                   if (dx1*dx1+dy1*dy1<dx2*dx2+dy2*dy2) {btx[0][i-1]=dpx1; bty[0][i-1]=dpy1;} else {btx[0][i-1]=dpx2; bty[0][i-1]=dpy2;}
00472               }
00473            }
00474        }
00475        
00476     }
00477     
00478     // for drawn-in hydrogens, work out which quadrant they are to be drawn on; done by working out angles to bonds, and finding the
00479     // lowest sum total of distance; note values: 1=east,2=north,3=west,4=south (+N*90 degrees)
00480     void SetupHydrogens()
00481     {
00482        hdir=new int[mol.NumAtoms()];
00483        
00484        for (int n=1;n<=mol.NumAtoms();n++)
00485        {
00486            hdir[n-1]=0; 
00487            if (hcount[n-1]==0) continue;
00488            int bonds[]=mol.AtomAdjList(n);
00489            int avoid1=0,avoid2=20,avoid3=10,avoid4=20; 
00490            for (int i=0;i<bonds.length + (bondInProgress && bipFrom==n ? 1 : 0);i++)
00491            {
00492               double x=i<bonds.length ? mol.AtomX(bonds[i]) : bipToX,y=i<bonds.length ? mol.AtomY(bonds[i]) : bipToY;
00493               double theta=Math.atan2(y-mol.AtomY(n),x-mol.AtomX(n))*180/Math.PI;
00494               double dt1=0-theta,dt2=90-theta,dt3=180-theta,dt4=270-theta;
00495               dt1=Math.abs(dt1+(dt1<-180 ? 360:0)+(dt1>180 ? -360 : 0));
00496               dt2=Math.abs(dt2+(dt2<-180 ? 360:0)+(dt2>180 ? -360 : 0));
00497               dt3=Math.abs(dt3+(dt3<-180 ? 360:0)+(dt3>180 ? -360 : 0));
00498               dt4=Math.abs(dt4+(dt4<-180 ? 360:0)+(dt4>180 ? -360 : 0));
00499               avoid1+=dt1<80 ? 80-dt1 : 0;
00500               avoid2+=dt2<80 ? 80-dt2 : 0;
00501               avoid3+=dt3<80 ? 80-dt3 : 0;
00502               avoid4+=dt4<80 ? 80-dt4 : 0;
00503            }
00504            if (avoid1<=avoid2 && avoid1<=avoid3 && avoid1<=avoid4) hdir[n-1]=1;
00505            else if (avoid3<=avoid2 && avoid3<=avoid4) hdir[n-1]=3;
00506            else if (avoid2<=avoid4) hdir[n-1]=2;
00507            else hdir[n-1]=4;
00508        }
00509     }
00510     
00511     // draws various types of highlighting, if appropriate
00512     void DrawBacklighting()
00513     {
00514        for (int n=1;n<=mol.NumAtoms();n++)
00515        {          
00516            boolean drag=false;
00517            if (dragged!=null) drag=dragged[n-1];
00518            
00519            if (selected[n-1] || n==highlightAtom || drag)
00520            {
00521               g.setColor(selected[n-1] ? colSelected : drag ? colDragged : colHighlight);
00522               double ext=Math.max(rw[n-1]*1.2,rh[n-1]*1.2);
00523               g.fill(new Ellipse2D.Double(px[n-1]-ext,py[n-1]-ext,2*ext,2*ext));
00524            }
00525        }
00526 
00527        if (highlightBond!=0) for (int n=0;n==0 || n<mol.BondOrder(highlightBond) && n<3;n++)
00528        {
00529            int bn=highlightBond-1;
00530            double x1=bfx[n][bn],y1=bfy[n][bn],x2=btx[n][bn],y2=bty[n][bn];
00531            double dx=x2-x1,dy=y2-y1;
00532            double norm=0.15*scale/Math.sqrt(dx*dx+dy*dy);
00533            double ox=norm*dy,oy=-norm*dx;
00534            Polygon pgn=new Polygon();
00535            pgn.addPoint((int)Math.round(x1+oy*0.5),(int)Math.round(y1-ox*0.5));
00536            pgn.addPoint((int)Math.round(x1-ox),(int)Math.round(y1-oy));
00537            pgn.addPoint((int)Math.round(x2-ox),(int)Math.round(y2-oy));
00538            pgn.addPoint((int)Math.round(x2-oy*0.5),(int)Math.round(y2+ox*0.5));
00539            pgn.addPoint((int)Math.round(x2+ox),(int)Math.round(y2+oy));
00540            pgn.addPoint((int)Math.round(x1+ox),(int)Math.round(y1+oy));
00541            g.setColor(colHighlight);
00542            g.fill(pgn);
00543        }
00544     }
00545 
00546     // draws all of the bonds
00547     void DrawBonds()
00548     {
00549        for (int n=1;n<=mol.NumBonds()+(bondInProgress ? 1 : 0);n++)
00550        {
00551            double x1=bfx[0][n-1],y1=bfy[0][n-1],x2=btx[0][n-1],y2=bty[0][n-1],dx=x2-x1,dy=y2-y1;
00552            int order=n<=mol.NumBonds() ? mol.BondOrder(n) : bipOrder;
00553            int type=n<=mol.NumBonds() ? mol.BondType(n) : bipType;
00554            boolean mappedBond=n<=mol.NumBonds() ? (mol.AtomMapNum(mol.BondFrom(n))>0 && mol.AtomMapNum(mol.BondTo(n))>0) : false;
00555            
00556            g.setColor(Color.BLACK);
00557            
00558            if (type==Molecule.BONDTYPE_INCLINED)
00559            {
00560               double norm=0.15*scale/Math.sqrt(dx*dx+dy*dy);
00561               double ox=norm*dy,oy=-norm*dx;
00562               Polygon pgn=new Polygon();
00563               pgn.addPoint((int)Math.round(x1),(int)Math.round(y1));
00564               pgn.addPoint((int)Math.round(x2-ox),(int)Math.round(y2-oy));
00565               pgn.addPoint((int)Math.round(x2+ox),(int)Math.round(y2+oy));
00566               g.fill(pgn);
00567            }
00568            else if (type==Molecule.BONDTYPE_DECLINED)
00569            {
00570               int nsteps=(int)Math.ceil(Math.sqrt(dx*dx+dy*dy)*0.15);
00571               double norm=0.15*scale/Math.sqrt(dx*dx+dy*dy);
00572               double ox=norm*dy,oy=-norm*dx;
00573               for (int i=0;i<=nsteps+1;i++)
00574               {
00575                   double cx=x1+i*dx/(nsteps+1),cy=y1+i*dy/(nsteps+1);
00576                   double ix=ox*i/(nsteps+1),iy=oy*i/(nsteps+1);
00577                   g.setStroke(new BasicStroke((float)(0.05*scale)));
00578                   g.draw(new Line2D.Double(cx-ix,cy-iy,cx+ix,cy+iy));
00579               }
00580            }
00581            else if (type==Molecule.BONDTYPE_UNKNOWN)
00582            {
00583               int nsteps=(int)Math.ceil(Math.sqrt(dx*dx+dy*dy)*0.2);
00584               double norm=0.2*scale/Math.sqrt(dx*dx+dy*dy);
00585               double ox=norm*dy,oy=-norm*dx;
00586               for (int i=0;i<=nsteps;i++)
00587               {
00588                   double ax=x1+i*dx/(nsteps+1),ay=y1+i*dy/(nsteps+1);
00589                   double cx=x1+(i+1)*dx/(nsteps+1),cy=y1+(i+1)*dy/(nsteps+1);
00590                   double bx=(ax+cx)/2,by=(ay+cy)/2;
00591                   int sign=i%2==0 ? 1 : -1;
00592                   g.setStroke(new BasicStroke((float)(0.05*scale)));
00593                   g.draw(new QuadCurve2D.Double(ax,ay,bx+sign*ox,by+sign*oy,cx,cy));
00594               }
00595            }
00596            else if (order==0)
00597            {
00598               int nsteps=(int)Math.ceil(Math.sqrt(dx*dx+dy*dy)*0.10);
00599               float radius=(float)(mappedBond ? 0.2*scale : 0.1*scale);
00600               for (int i=0;i<=nsteps+1;i++)
00601               {
00602                   double cx=x1+i*dx/(nsteps+1),cy=y1+i*dy/(nsteps+1);
00603                   g.fill(new Ellipse2D.Double(cx-0.05*scale,cy-0.05*scale,radius,radius));
00604               }
00605            }
00606            else
00607            {
00608               float thickness=(float)(mappedBond ? 0.125*scale : 0.075*scale);
00609               g.setStroke(new BasicStroke(thickness,BasicStroke.CAP_ROUND,BasicStroke.JOIN_ROUND));
00610               for (int i=order<=3 ? order-1 : 2;i>=0;i--)
00611                   g.draw(new Line2D.Double(bfx[i][n-1],bfy[i][n-1],btx[i][n-1],bty[i][n-1]));
00612            }
00613        }
00614     }
00615     
00616     // draw the atoms proper, as well as standard annotations such as hydrogen atoms and charges
00617     void DrawAtoms()
00618     {
00619        // atoms first
00620        for (int n=1;n<=mol.NumAtoms();n++)
00621        {
00622            if (atomLabel[n-1].length()==0) continue;
00623            g.setColor(showMode==SHOW_INDEXES ? Color.BLUE : showMode==SHOW_RINGID ? Color.RED : 
00624                      showMode==SHOW_PRIORITY ? Color.GREEN.darker() : showMode==SHOW_MAPNUM ? Color.MAGENTA : Color.BLACK);
00625            double hx=px[n-1],hy=py[n-1] -0.1*metrics.getAscent(); // (& little fudge for font alignment)
00626            
00627            g.setFont(mol.AtomMapNum(n)>0 && (showMode==SHOW_ELEMENTS || showMode==SHOW_ALL_ELEMENTS) ? boldFont : font);
00628            g.drawString(atomLabel[n-1],(float)(hx-rw[n-1]),(float)(hy+0.5*metrics.getAscent()));
00629            
00630            if (hcount[n-1]>0)
00631            {
00632               int bigHWid=metrics.stringWidth("H");
00633               int subHWid=hcount[n-1]>1 ? smallMetrics.stringWidth(String.valueOf(hcount[n-1])) : 0;
00634               if (hdir[n-1]==1) {hx+=rw[n-1]; hy+=0.5*metrics.getAscent();}
00635               else if (hdir[n-1]==3) {hx-=rw[n-1]+bigHWid+subHWid; hy+=0.5*metrics.getAscent();}
00636               else if (hdir[n-1]==2) {hx-=rw[n-1]; hy-=0.5*metrics.getAscent();}
00637               else if (hdir[n-1]==4) {hx-=rw[n-1]; hy+=1.5*metrics.getAscent();}
00638               g.setFont(font);
00639               // hydrogen color
00640               g.setColor(Color.BLACK);
00641               g.drawString("H",(float)hx,(float)hy);
00642               if (hcount[n-1]>1) 
00643               {
00644                   g.setFont(smallFont);
00645                   g.drawString(String.valueOf(hcount[n-1]),(float)hx+bigHWid,(float)hy);
00646               }
00647            }
00648        }
00649        
00650        // annotations next
00651        final int ANNOT_PLUS=1,ANNOT_MINUS=2,ANNOT_RAD=3;
00652        ArrayList<Integer> annot=new ArrayList<Integer>();
00653        double usz=scale*0.3;
00654 
00655        for (int n=1;n<=mol.NumAtoms();n++)
00656        {
00657            annot.clear();
00658            int chg=mol.AtomCharge(n);
00659            while (chg<0) {annot.add(ANNOT_MINUS); chg++;}
00660            while (chg>0) {annot.add(ANNOT_PLUS); chg--;}
00661            int rad=mol.AtomUnpaired(n);
00662            while (rad>0) {annot.add(ANNOT_RAD); rad--;}
00663            
00664            if (annot.size()==0) continue;
00665            
00666            double bw=annot.size()*usz,bh=usz;
00667            final int ANG_INCR=5,CLOCK_SZ=360/ANG_INCR,SAMP_SZ=3*CLOCK_SZ;
00668            double bestAng=0,bestExt=0,bestScore=Double.MAX_VALUE;
00669            for (int i=1;i<=3;i++) for (int j=0;j<CLOCK_SZ;j++)
00670            {
00671               double ang=j*ANG_INCR;
00672               if (hdir[n-1]==1 && (ang<=45 || ang>=315)) continue;
00673               if (hdir[n-1]==4 && ang>=45 && ang<=135) continue;
00674               if (hdir[n-1]==3 && ang>=135 && ang<=225) continue;
00675               if (hdir[n-1]==2 && ang>=225 && ang<=315) continue;
00676               
00677               double ext=0.5*(rw[n-1]+rw[n-1])+i*scale*0.25;
00678               double from45=Math.min(Math.abs(315-ang + (ang<135 ? -360 : 0)),90);
00679               double score=10*ext + 0.01*from45;
00680               if (score>bestScore) continue;
00681        
00682               double ax=px[n-1]+ext*Math.cos(ang*Math.PI/180.0);
00683               double ay=py[n-1]+ext*Math.sin(ang*Math.PI/180.0);
00684               
00685               for (int k=1;k<=mol.NumBonds();k++)
00686               {
00687                   double dsq=0;
00688                   double x1=px[mol.BondFrom(k)-1],y1=py[mol.BondFrom(k)-1],x2=px[mol.BondTo(k)-1],y2=py[mol.BondTo(k)-1];
00689                   double vx=x2-x1,vy=y2-y1,wx=ax-x1,wy=ay-y1;
00690                   double c1=vx*wx+vy*wy;
00691                   if (c1<=0) dsq=(ax-x1)*(ax-x1)+(ay-y1)*(ay-y1);
00692                   else 
00693                   {
00694                      double c2=vx*vx+vy*vy;
00695                      if (c2<=c1) dsq=(ax-x2)*(ax-x2)+(ay-y2)*(ay-y2);
00696                      else
00697                      {
00698                          double b=c1/c2;
00699                          double bx=x1+b*vx,by=y1+b*vy;
00700                          dsq=(ax-bx)*(ax-bx)+(ay-by)*(ay-by);
00701                      }
00702                   } 
00703                   
00704                   score+=100/Math.max(dsq,0.0001);
00705               }             
00706        
00707               if (score<bestScore) 
00708               {
00709                   bestAng=ang;
00710                   bestExt=ext;
00711                   bestScore=score;
00712               }
00713            }
00714            
00715            double ax=px[n-1]+bestExt*Math.cos(bestAng*Math.PI/180.0)-0.5*bw;
00716            double ay=py[n-1]+bestExt*Math.sin(bestAng*Math.PI/180.0)-0.5*bh;
00717            g.setColor(Color.BLACK);
00718            for (int i=0;i<annot.size();i++)
00719            {
00720               int type=annot.get(i).intValue();
00721               double x1=ax+0.2*usz,x2=ax+0.8*usz,y1=ay+0.2*usz,y2=ay+0.8*usz;
00722               if (type==ANNOT_MINUS || type==ANNOT_PLUS)
00723               {
00724                   g.draw(new Line2D.Double(x1,0.5*(y1+y2),x2,0.5*(y1+y2)));
00725               }
00726               if (type==ANNOT_PLUS)
00727               {
00728                   g.draw(new Line2D.Double(0.5*(x1+x2),y1,0.5*(x1+x2),y2));
00729               }
00730               if (type==ANNOT_RAD)
00731               {
00732                   g.fill(new Ellipse2D.Double(ax+0.2*usz,ay+0.2*usz,0.6*usz,0.6*usz));
00733               }
00734               ax+=usz;
00735            }
00736        }
00737     }
00738     
00739     // draw the less common annotations, such as stereo labels, atom mapping numbers, etc.
00740     void DrawAnnotations()
00741     {
00742        if (showSter)
00743        {
00744            for (int n=1,chi;n<=mol.NumAtoms();n++) if ((chi=mol.AtomChirality(n))!=Molecule.STEREO_NONE)
00745            {
00746               String label=chi==Molecule.STEREO_POS ? "R" : chi==Molecule.STEREO_NEG ? "S" : "R/S";
00747               g.setColor(Color.BLUE);
00748               g.setFont(font);
00749               g.drawString(label,(float)(px[n-1]-0.5*metrics.stringWidth(label)),(float)(py[n-1]+metrics.getHeight()));
00750            }
00751            for (int n=1,chi;n<=mol.NumBonds();n++) if ((chi=mol.BondStereo(n))!=Molecule.STEREO_NONE)
00752            {
00753               String label=chi==Molecule.STEREO_POS ? "Z" : chi==Molecule.STEREO_NEG ? "E" : "E/Z";
00754               int i1=mol.BondFrom(n)-1,i2=mol.BondTo(n)-1;
00755               g.setColor(Color.BLUE);
00756               g.setFont(font);
00757               g.drawString(label,(float)(0.5*(px[i1]+px[i2]-metrics.stringWidth(label))),
00758                                (float)(0.5*(py[i1]+py[i2]+metrics.getHeight())));
00759            }
00760        }
00761     }
00762 
00763     // draw the effects of various editing-in-progress actions
00764     void DrawEffects()
00765     {
00766        if (atomInProgress)
00767        {
00768            g.setColor(Color.BLUE);
00769            g.setFont(font);
00770            double tx=AngToX(aipToX),ty=AngToY(aipToY);
00771            g.drawString(aipLabel,(float)(tx-0.5*metrics.stringWidth(aipLabel)),(float)(ty+0.4*metrics.getAscent()));
00772        }
00773 
00774        if (newBondLine)
00775        {
00776            g.setColor(Color.BLACK);
00777            g.draw(new Line2D.Double(AngToX(nblX1),AngToY(nblY1),AngToX(nblX2),AngToY(nblY2)));
00778        }
00779        
00780        if (dragSelect)
00781        {
00782            g.setXORMode(Color.YELLOW);
00783            int x=dslX1,y=dslY1,w=dslX2-dslX1,h=dslY2-dslY1;
00784            if (w<0) {w=-w; x-=w;}
00785            if (h<0) {h=-h; y-=h;}
00786            g.drawRect(x,y,w,h);
00787            g.setXORMode(Color.BLACK);
00788        }
00789        
00790        if (dragScale)
00791        {
00792            g.setColor(Color.BLACK);
00793            g.setStroke(new BasicStroke(1.1F));
00794            for (int n=1;n<=mol.NumAtoms();n++) if (selected[n-1])
00795            {
00796               double sx=AngToX((mol.AtomX(n)-dscCX)*dscExtMul+dscCX),sy=AngToY((mol.AtomY(n)-dscCY)*dscExtMul+dscCY);
00797               g.draw(new Ellipse2D.Double(sx-scale*0.3,sy-scale*0.3,scale*0.6,scale*0.6));
00798            }
00799        }
00800        
00801        if (dragMove)
00802        {
00803            g.setColor(Color.BLACK);
00804            g.setStroke(new BasicStroke(1.1F));
00805            for (int n=1;n<=mol.NumAtoms();n++) if (selected[n-1])
00806            {
00807               double sx=px[n-1]+dmvDX,sy=py[n-1]+dmvDY;
00808               g.draw(new Ellipse2D.Double(sx-scale*0.3,sy-scale*0.3,scale*0.6,scale*0.6));
00809               if (dmvCopy)
00810               {
00811                   g.draw(new Line2D.Double(sx-scale*0.15,sy,sx+scale*0.15,sy));
00812                   g.draw(new Line2D.Double(sx,sy-scale*0.15,sx,sy+scale*0.15));
00813               }
00814            }
00815        }
00816        
00817        if (dragRotate)
00818        {
00819            double thrad=droTheta*Math.PI/180;
00820            g.setColor(Color.RED);
00821            g.setStroke(new BasicStroke(0.5F,BasicStroke.CAP_ROUND,BasicStroke.JOIN_ROUND,1F,new float[]{2,2},0));
00822            g.draw(new Line2D.Double(droX,droY,droX+50,droY));
00823            g.setStroke(new BasicStroke(1F));
00824            g.draw(new Line2D.Double(droX,droY,droX+50*Math.cos(-thrad),droY+50*Math.sin(-thrad)));
00825            g.draw(new Arc2D.Double(droX-20,droY-20,40,40,0,droTheta,Arc2D.OPEN));
00826 
00827            int ty=(int)((droTheta>25 || (droTheta<0 && droTheta>=-25)) ? droY-5 : droY+5+txh);
00828            DecimalFormat fmt=new DecimalFormat("0");
00829            g.drawString((droTheta>0?"+":"")+fmt.format(Math.round(droTheta)),(int)(droX+25),ty);
00830            
00831            double ax=XToAng(droX),ay=YToAng(droY);
00832            g.setStroke(new BasicStroke(1.1F));
00833            for (int n=1;n<=mol.NumAtoms();n++) if (selected[n-1])
00834            {
00835               double rx=mol.AtomX(n)-ax,ry=mol.AtomY(n)-ay;
00836               double rth=Math.atan2(ry,rx),ext=Math.sqrt(rx*rx+ry*ry);
00837               rx=ax+ext*Math.cos(rth+thrad);
00838               ry=ay+ext*Math.sin(rth+thrad);
00839               g.draw(new Ellipse2D.Double(AngToX(rx)-scale*0.3,AngToY(ry)-scale*0.3,scale*0.6,scale*0.6));
00840            }
00841        }
00842                   
00843        if (outlineTemplate)
00844        {
00845            g.setColor(new Color(128,128,128));
00846            g.setStroke(new BasicStroke(1));
00847            for (int n=1;n<=oltMol.NumBonds();n++)
00848            {
00849               int from=oltMol.BondFrom(n),to=oltMol.BondTo(n);
00850               g.draw(new Line2D.Double(AngToX(oltMol.AtomX(from)),AngToY(oltMol.AtomY(from)),
00851                                     AngToX(oltMol.AtomX(to)),AngToY(oltMol.AtomY(to))));
00852            }
00853        }
00854     }
00855     
00856     // bring attention to structures which are malformed
00857     void DrawCorrections()
00858     {
00859        // see if any atoms severely overlap, and if so, draw a red circle around them
00860        for (int i=1;i<=mol.NumAtoms()-1;i++)
00861        for (int j=i+1;j<=mol.NumAtoms();j++)
00862        {
00863            double dx=mol.AtomX(i)-mol.AtomX(j),dy=mol.AtomY(i)-mol.AtomY(j);
00864            if (dx*dx+dy*dy<0.2*0.2)
00865            {
00866               g.setColor(Color.RED);
00867               g.setStroke(new BasicStroke(0.5F));
00868               g.draw(new Ellipse2D.Double(px[i-1]-scale*0.25,py[i-1]-scale*0.25,scale*0.5,scale*0.5));
00869 
00870            }
00871        }
00872     }
00873     
00874     // translation of screen & molecule coordinates    
00875     double AngToX(double AX) {return (offsetX+AX)*scale;}
00876     double AngToY(double AY) {return (offsetY-AY)*scale;}
00877     double XToAng(double PX) {return (PX/scale)-offsetX;}
00878     double YToAng(double PY) {return (-PY/scale)+offsetY;}
00879 }