Subversion Repositories wimsdev

Rev

Rev 7246 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

  1. /*
  2.     WIMSchem Elements: Chemistry molecular diagram drawing tool.
  3.    
  4.     (c) 2007 Dr. Alex M. Clark
  5.    
  6.     Released as GNUware, under the Gnu Public License (GPL)
  7.    
  8.     See www.gnu.org for details.
  9. */
  10.  
  11. package WIMSchem;
  12.  
  13. import java.util.*;
  14. import java.text.*;
  15. import java.awt.*;
  16. import java.awt.geom.*;
  17.  
  18. /*
  19.     A class dedicated to the drawing of a molecule. Lifecycle: provide the minimal input data, add any additional parameters as necessary,
  20.     draw onto a canvas of some kind, possibly grab back some data, then destroy.
  21. */
  22.  
  23. public class DrawMolecule
  24. {
  25.     Molecule mol;
  26.     Graphics2D g;
  27.  
  28.     Color backgr=Color.WHITE;
  29.     Color colHighlight=null,colSelected=null,colDragged=null;
  30.     //,colExternalSelection=null;
  31.     boolean showHydr=false;
  32.     int showMode=SHOW_ALL_ELEMENTS;
  33.     boolean showSter=false;
  34.    
  35.     double offsetX=0,offsetY=0; // in Angstrom units
  36.     double scale=DEFSCALE; // pixels per Angstrom
  37.  
  38.     String[] atomLabel=null;
  39.     boolean[] expl=null;
  40.     int[] hcount=null;
  41.     double[] px=null,py=null,rw=null,rh=null;
  42.     double[][] bfx=null,bfy=null,btx=null,bty=null;
  43.     int[] hdir=null;
  44.  
  45.     Font font=null,smallFont=null,boldFont=null;
  46.     FontMetrics metrics=null,smallMetrics=null,boldMetrics=null;
  47.     int txh=0;
  48.  
  49.  
  50.     boolean[] selected=null,dragged=null;
  51.     int highlightAtom=0,highlightBond=0;
  52.  
  53.     boolean bondInProgress=false;
  54.     int bipFrom,bipOrder,bipType;
  55.     double bipToX,bipToY;
  56.    
  57.     boolean atomInProgress=false;
  58.     String aipLabel;
  59.     double aipToX,aipToY;
  60.    
  61.     boolean newBondLine=false;
  62.     double nblX1,nblY1,nblX2,nblY2;
  63.  
  64.     boolean dragSelect=false;
  65.     int dslX1,dslY1,dslX2,dslY2;
  66.    
  67.     boolean dragScale=false;
  68.     double dscCX,dscCY,dscExtMul;
  69.  
  70.     boolean dragMove=false;
  71.     double dmvDX,dmvDY;
  72.     boolean dmvCopy;
  73.    
  74.     boolean dragRotate=false;
  75.     double droTheta;
  76.     int droX,droY;
  77.    
  78.     boolean outlineTemplate=false;
  79.     Molecule oltMol;
  80.     // jm.evers 27/12/2008  
  81.     boolean viewC=MainPanel.viewC;
  82.     //
  83.     // public constants
  84.     public static final double DEFSCALE=20; // default # Angstroms per pixel
  85.     public static final int SHOW_ELEMENTS=0;
  86.     public static final int SHOW_ALL_ELEMENTS=1;
  87.     public static final int SHOW_INDEXES=2;
  88.     public static final int SHOW_RINGID=3;
  89.     public static final int SHOW_PRIORITY=4;
  90.     public static final int SHOW_MAPNUM=5;
  91.  
  92.     // constructor
  93.  
  94.     public DrawMolecule(Molecule Mol,Graphics2D Gr)
  95.     {
  96.         mol=Mol;
  97.         g=Gr;
  98.     }
  99.    
  100.     // methods to provide other drawing options...
  101.    
  102.     public void SetBackground(Color Col) {backgr=Col;}
  103.     public void SetShowHydr(boolean ShowH) {showHydr=ShowH;}
  104.     public void SetShowMode(int Mode) {showMode=Mode;}
  105.     public void SetShowStereo(boolean ShowSter) {showSter=ShowSter;}
  106.     public void SetTransform(double OX,double OY,double Scale) {offsetX=OX; offsetY=OY; scale=Scale;}
  107.     public void SetSelected(boolean[] Selected,boolean[] Dragged) {selected=Selected; dragged=Dragged;}
  108.     public void SetHighlight(int Atom,int Bond) {highlightAtom=Atom; highlightBond=Bond;}
  109.  
  110.     public void BondInProgress(int From,double X,double Y,int Order,int Type)
  111.     {
  112.         bondInProgress=true;
  113.         bipFrom=From;
  114.         bipToX=X;
  115.         bipToY=Y;
  116.         bipOrder=Order;
  117.         bipType=Type;
  118.     }
  119.     public void AtomInProgress(String Label,double X,double Y)
  120.     {
  121.         atomInProgress=true;
  122.         aipLabel=Label;
  123.         aipToX=X;
  124.         aipToY=Y;
  125.     }
  126.     public void NewBondLine(double X1,double Y1,double X2,double Y2)
  127.     {
  128.         // !! bond order and type should be accounted for...
  129.         newBondLine=true;
  130.         nblX1=X1;
  131.         nblY1=Y1;
  132.         nblX2=X2;
  133.         nblY2=Y2;
  134.     }
  135.     public void DragSelect(int X1,int Y1,int X2,int Y2)
  136.     {
  137.         dragSelect=true;
  138.         dslX1=X1;
  139.         dslY1=Y1;
  140.         dslX2=X2;
  141.         dslY2=Y2;
  142.     }
  143.     public void DragScale(double CX,double CY,double ExtMul)
  144.     {
  145.         dragScale=true;
  146.         dscCX=CX;
  147.         dscCY=CY;
  148.         dscExtMul=ExtMul;
  149.     }
  150.     public void DragMove(double DX,double DY,boolean Copy)
  151.     {
  152.         dragMove=true;
  153.         dmvDX=DX;
  154.         dmvDY=DY;
  155.         dmvCopy=Copy;
  156.     }
  157.     public void DragRotate(double Theta,int X,int Y)
  158.     {
  159.         dragRotate=true;
  160.         droTheta=Theta;
  161.         droX=X;
  162.         droY=Y;
  163.     }
  164.     public void OutlineTemplate(Molecule Templ)
  165.     {
  166.         outlineTemplate=true;
  167.         oltMol=Templ;
  168.     }
  169.    
  170.     // methods to extract calculated properties for future reference
  171.    
  172.     public double[] GetPX() {return px;}
  173.     public double[] GetPY() {return py;}
  174.     public double[] GetRW() {return rw;}
  175.     public double[] GetRH() {return rh;}
  176.     public double[][] GetBFX() {return bfx;}
  177.     public double[][] GetBFY() {return bfy;}
  178.     public double[][] GetBTX() {return btx;}
  179.     public double[][] GetBTY() {return bty;}
  180.    
  181.     // perform the actual drawing
  182.    
  183.     public void Draw()
  184.     {
  185.         SetupColours();
  186.         SetupFonts();
  187.         SetupLabels();
  188.         SetupPositions();
  189.         SetupHydrogens();
  190.         DrawBacklighting();
  191.         // jm.evers
  192.         DrawExternalAtomSelection();
  193.         DrawBonds();
  194.         DrawAtoms();
  195.         DrawEffects();
  196.         DrawAnnotations();
  197.         DrawCorrections();
  198.     }
  199.    
  200.     // private methods
  201.    
  202.     // decides on particular colors
  203.     void SetupColours()
  204.     {
  205.         if (colHighlight==null) colHighlight=backgr.darker();
  206.         if (colSelected==null) colSelected=new Color(colHighlight.getRed(),colHighlight.getGreen(),255);
  207.         if (colDragged==null) colDragged=new Color(colHighlight.getRed(),192,255);
  208.         //if (colExternalSelection==null) colExternalSelection=new Color(255,0,0,50);
  209.        
  210.     }
  211.  
  212.     // creates the fonts and metrics
  213.     void SetupFonts()
  214.     {
  215.    
  216.         font=new Font("SansSerif",Font.PLAIN,(int)(0.7*scale));
  217.         g.setFont(font);
  218.         metrics=g.getFontMetrics();
  219.         txh=(int)(metrics.getAscent()*0.85);
  220.        
  221.         smallFont=new Font("SansSerif",Font.PLAIN,(int)(0.35*scale));
  222.         g.setFont(smallFont);
  223.         smallMetrics=g.getFontMetrics();
  224.        
  225.         boldFont=new Font("SansSerif",Font.BOLD,(int)(0.7*scale));
  226.         g.setFont(boldFont);
  227.         boldMetrics=g.getFontMetrics();
  228.         font=new Font("SansSerif",Font.PLAIN,(int)(0.7*scale));
  229.  
  230.  
  231.  
  232.     }
  233.    
  234.     // decides what exactly needs to be drawn for each atom label
  235.     void SetupLabels(){
  236.         if (selected==null) selected=new boolean[mol.NumAtoms()];
  237.         if (dragged==null) dragged=new boolean[mol.NumAtoms()];
  238.    
  239.         if (atomLabel!=null && expl!=null && hcount!=null) return;
  240.    
  241.         atomLabel=new String[mol.NumAtoms()];
  242.         expl=new boolean[mol.NumAtoms()];
  243.         hcount=new int[mol.NumAtoms()];
  244.         for (int n=1;n<=mol.NumAtoms();n++){
  245.             // jm.evers: to accomodate param elements="no" ... only for Carbon
  246.             // all other elements will still be shown, ofcourse
  247.             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)) : "";
  248.             else if (showMode==SHOW_ELEMENTS) atomLabel[n-1]=mol.AtomExplicit(n) ? mol.AtomElement(n) : "";
  249.             else if (showMode==SHOW_ALL_ELEMENTS) atomLabel[n-1]=mol.AtomElement(n);
  250.             else if (showMode==SHOW_INDEXES) atomLabel[n-1]=String.valueOf(n);
  251.             else if (showMode==SHOW_RINGID) atomLabel[n-1]=String.valueOf(mol.AtomRingBlock(n));
  252.             else if (showMode==SHOW_PRIORITY) atomLabel[n-1]=String.valueOf(mol.AtomPriority(n));
  253.             else if (showMode==SHOW_MAPNUM) atomLabel[n-1]=mol.AtomMapNum(n)>0 ? String.valueOf(mol.AtomMapNum(n)) : "";
  254.             else atomLabel[n-1]="?";
  255.             expl[n-1]=atomLabel[n-1].length()>0;
  256.             hcount[n-1]=showHydr && (showMode==SHOW_ELEMENTS || showMode==SHOW_ALL_ELEMENTS) && expl[n-1] ? mol.AtomHydrogens(n) : 0;
  257.         }
  258.     }
  259.    
  260.     // configure the positions of all of the atoms, and their associated bonds; apply whatever aesthetic corrections are deemed necessary
  261.     void SetupPositions()
  262.     {
  263.         if (px!=null && py!=null && rw!=null && rh!=null && bfx!=null && bfy!=null && btx!=null && bty!=null) return;
  264.        
  265.         px=new double[mol.NumAtoms()];
  266.         py=new double[mol.NumAtoms()];
  267.         rw=new double[mol.NumAtoms()];
  268.         rh=new double[mol.NumAtoms()];
  269.         bfx=new double[3][mol.NumBonds()+1];
  270.         bfy=new double[3][mol.NumBonds()+1];
  271.         btx=new double[3][mol.NumBonds()+1];
  272.         bty=new double[3][mol.NumBonds()+1];
  273.  
  274.         for (int n=1;n<=mol.NumAtoms();n++)
  275.         {
  276.             px[n-1]=AngToX(mol.AtomX(n));
  277.             py[n-1]=AngToY(mol.AtomY(n));
  278.             if (expl[n-1])
  279.             {
  280.                 if (mol.AtomMapNum(n)==0)
  281.                     rw[n-1]=0.5*metrics.stringWidth(atomLabel[n-1]);
  282.                 else
  283.                     rw[n-1]=0.5*boldMetrics.stringWidth(atomLabel[n-1]);
  284.                 rh[n-1]=0.5*(metrics.getAscent()+metrics.getDescent());
  285.             }
  286.             else
  287.             {
  288.                 rw[n-1]=rh[n-1]=txh*0.4;
  289.             }
  290.         }
  291.        
  292.         // figure out which double bonds should go on one particular side
  293.         int bondside[]=new int[mol.NumBonds()];
  294.         for (int n=0;n<mol.NumBonds();n++) bondside[n]=0;
  295.         int ring6[][]=mol.FindRingSize(6),nring6=ring6.length;
  296.         for (int n=0;n<ring6.length;n++) // convert to bond indexes
  297.         {
  298.             int a=ring6[n][0];
  299.             for (int i=0;i<6;i++) ring6[n][i]=mol.FindBond(ring6[n][i],i==5 ? a : ring6[n][i+1]);
  300.         }
  301.         int r6score[]=new int[ring6.length];
  302.         while (true)
  303.         {
  304.             int best=-1;
  305.             for (int n=0;n<nring6;n++)
  306.             {
  307.                 r6score[n]=0;
  308.                 for (int i=0;i<6;i++) if (mol.BondOrder(ring6[n][i])==2 && bondside[ring6[n][i]-1]==0) r6score[n]++;
  309.                 if (r6score[n]>0 && (best<0 || r6score[n]>r6score[best])) best=n;
  310.             }
  311.             if (best<0) break;
  312.             for (int n=0;n<6;n++)
  313.             {
  314.                 int bond=ring6[best][n];
  315.                 if (mol.BondOrder(bond)!=2 || bondside[bond-1]!=0) continue;
  316.                 int from=mol.BondFrom(bond),to=mol.BondTo(bond);
  317.                 int numLeft=0,numRight=0,numFrom=0,numTo=0;
  318.                 double thbond=Math.atan2(mol.AtomY(to)-mol.AtomY(from),mol.AtomX(to)-mol.AtomX(from));
  319.                 for (int i=0;i<6;i++) if (i!=n)
  320.                 {
  321.                     int o=mol.BondOther(ring6[best][i],from);
  322.                     double theta;
  323.                     if (o>0)
  324.                     {
  325.                         theta=Math.atan2(mol.AtomY(o)-mol.AtomY(from),mol.AtomX(o)-mol.AtomX(from));
  326.                         numFrom++;
  327.                     }
  328.                     else
  329.                     {
  330.                         o=mol.BondOther(ring6[best][i],to);
  331.                         if (o>0)
  332.                         {
  333.                             theta=Math.atan2(mol.AtomY(o)-mol.AtomY(to),mol.AtomX(o)-mol.AtomX(to));
  334.                             numTo++;
  335.                         }
  336.                         else continue;
  337.                     }
  338.                     theta=theta-thbond;
  339.                     theta+=theta>Math.PI ? -2*Math.PI : theta<-Math.PI ? 2*Math.PI : 0;
  340.                     if (theta<0) numLeft++;
  341.                     if (theta>0) numRight++;
  342.                 }
  343.                 if (numFrom>0 && numTo>0) bondside[bond-1]=numLeft>numRight ? -1 : 1;
  344.             }
  345.             if (best<nring6-1) ring6[best]=ring6[--nring6];
  346.         }
  347.         // remaining bonds, if they are in a ring or are unevenly balanced
  348.         for (int n=1;n<=mol.NumBonds();n++) if (mol.BondOrder(n)==2 && bondside[n-1]==0)
  349.         {
  350.             int from=mol.BondFrom(n),to=mol.BondTo(n);
  351.             int[] adj1=mol.AtomAdjList(from),adj2=mol.AtomAdjList(to);
  352.             if (adj1.length>=3 && adj2.length>=3 && !mol.BondInRing(n)) continue; // evenly balanced chain bond, leave in middle
  353.             if ((adj1.length==1 && adj2.length>=3) || (adj2.length==1 && adj1.length>=3)) continue; // ketone-like bond
  354.             int numLeft=0,numRight=0,numFrom=0,numTo=0;
  355.             double thbond=Math.atan2(mol.AtomY(to)-mol.AtomY(from),mol.AtomX(to)-mol.AtomX(from));
  356.             for (int i=0;i<adj1.length;i++) if (adj1[i]!=to)
  357.             {
  358.                 double theta=Math.atan2(mol.AtomY(adj1[i])-mol.AtomY(from),mol.AtomX(adj1[i])-mol.AtomX(from))-thbond;
  359.                 theta+=theta>Math.PI ? -2*Math.PI : theta<-Math.PI ? 2*Math.PI : 0;
  360.                 int v=mol.BondInRing(n) && mol.AtomRingBlock(from)==mol.AtomRingBlock(adj1[i]) ? 2 : 1;
  361.                 if (theta<0) numLeft+=v;
  362.                 if (theta>0) numRight+=v;
  363.             }
  364.             for (int i=0;i<adj2.length;i++) if (adj2[i]!=from)
  365.             {
  366.                 double theta=Math.atan2(mol.AtomY(adj2[i])-mol.AtomY(to),mol.AtomX(adj2[i])-mol.AtomX(to))-thbond;
  367.                 theta+=theta>Math.PI ? -2*Math.PI : theta<-Math.PI ? 2*Math.PI : 0;
  368.                 int v=mol.BondInRing(n) && mol.AtomRingBlock(to)==mol.AtomRingBlock(adj2[i]) ? 2 : 1;
  369.                 if (theta<0) numLeft+=v;
  370.                 if (theta>0) numRight+=v;
  371.             }
  372.             if (numLeft!=numRight) bondside[n-1]=numLeft>numRight ? -1 : 1;
  373.         }
  374.        
  375.         int extraAtom=bondInProgress ? 1 : 0;
  376.        
  377.         // define positions for the bonds
  378.         for (int n=1;n<=mol.NumBonds() + extraAtom;n++)
  379.         {
  380.             int from,to,order,type,side;
  381.             double x1,y1,x2,y2,w1,h1,w2,h2;
  382.             boolean expl1,expl2;
  383.             if (n<=mol.NumBonds())
  384.             {
  385.                 from=mol.BondFrom(n); to=mol.BondTo(n); order=mol.BondOrder(n); type=mol.BondType(n); side=bondside[n-1];
  386.                 x1=px[from-1]; y1=py[from-1]; x2=px[to-1]; y2=py[to-1];
  387.                 w1=rw[from-1]; h1=rh[from-1]; w2=rw[to-1]; h2=rh[to-1];
  388.                 expl1=expl[from-1]; expl2=expl[to-1];
  389.             }
  390.             else // bond "in progress"
  391.             {
  392.                 from=bipFrom; to=0; order=bipOrder; type=bipType; side=0;
  393.                 x1=px[from-1]; y1=py[from-1]; x2=AngToX(bipToX); y2=AngToY(bipToY);
  394.                 w1=rw[from-1]; h1=rh[from-1]; w2=0; h2=0;
  395.                 expl1=expl[from-1]; expl2=false;
  396.             }
  397.  
  398.             double dx=x2-x1,dy=y2-y1;
  399.             if (Math.abs(dx)<0.001)
  400.             {
  401.                 dx=0;
  402.                 if (expl1) y1+=h1 * (dy>0 ? 1 : -1);
  403.                 if (expl2) y2+=h2 * (dy>0 ? -1 : 1);
  404.             }
  405.             else if (Math.abs(dy)<0.001)
  406.             {
  407.                 dy=0;
  408.                 if (expl1) x1+=w1 * (dx>0 ? 1 : -1);
  409.                 if (expl2) x2+=w2 * (dx>0 ? -1 : 1);
  410.             }
  411.             else
  412.             {
  413.                 double xy=Math.abs(dx/dy),yx=Math.abs(dy/dx);
  414.                 if (expl1)
  415.                 {
  416.                     x1+=(w1*h1*xy/(xy*h1+w1)) * (dx>0 ? 2 : -2);
  417.                     y1+=(w1*h1*yx/(yx*h1+w1)) * (dy>0 ? 2 : -2);
  418.                 }
  419.                 if (expl2)
  420.                 {
  421.                     x2+=(w2*h2*xy/(xy*h2+w2)) * (dx>0 ? -2 : 2);
  422.                     y2+=(w2*h2*yx/(yx*h2+w2)) * (dy>0 ? -2 : 2);
  423.                 }
  424.             }
  425.            
  426.             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;}
  427.            
  428.             if (order==2 || order==3)
  429.             {
  430.                 double norm=0.15*scale/Math.sqrt(dx*dx+dy*dy);
  431.                 double ox=Math.signum(dy)*Math.ceil(norm*Math.abs(dy)*(order==3 ? 1 : 0.5));
  432.                 double oy=-Math.signum(dx)*Math.ceil(norm*Math.abs(dx)*(order==3 ? 1 : 0.5));
  433.                 if (order==2)
  434.                 {
  435.                     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);
  436.                     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);
  437.                    
  438.                     if (n<=mol.NumBonds() && side!=0 && mol.AtomRingBlock(from)>0 && mol.BondInRing(n))
  439.                     {
  440.                         int w=side<0 ? 0 : 1;
  441.                         if (!expl1) {bfx[w][n-1]+=norm*dx; bfy[w][n-1]+=norm*dy;}
  442.                         if (!expl2) {btx[w][n-1]-=norm*dx; bty[w][n-1]-=norm*dy;}
  443.                     }
  444.                 }
  445.                 else
  446.                 {
  447.                     bfx[1][n-1]-=ox; bfy[1][n-1]-=oy; btx[1][n-1]-=ox; bty[1][n-1]-=oy;
  448.                     bfx[2][n-1]+=ox; bfy[2][n-1]+=oy; btx[2][n-1]+=ox; bty[2][n-1]+=oy;
  449.                 }
  450.             }
  451.         }
  452.         // special case for non-ring non-explicit double bond endpoints... neighbouring single bonds get snapped "to"
  453.         for (int n=1;n<=mol.NumAtoms();n++) if (atomLabel[n-1].length()==0 && mol.AtomRingBlock(n)==0)
  454.         {
  455.             boolean any=false;
  456.             double dpx1=0,dpy1=0,dpx2=0,dpy2=0;
  457.             for (int i=1;i<=mol.NumBonds();i++) if (mol.BondOrder(i)==2)
  458.             {
  459.                 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;}
  460.                 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;}
  461.             }
  462.             if (!any) continue;
  463.             for (int i=1;i<=mol.NumBonds();i++) if (mol.BondOrder(i)==1)
  464.             {
  465.                 if (mol.BondFrom(i)==n)
  466.                 {
  467.                     double dx1=dpx1-btx[0][i-1],dy1=dpy1-bty[0][i-1];
  468.                     double dx2=dpx2-btx[0][i-1],dy2=dpy2-bty[0][i-1];
  469.                     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;}
  470.                 }
  471.                 else if (mol.BondTo(i)==n)
  472.                 {
  473.                     double dx1=dpx1-bfx[0][i-1],dy1=dpy1-bfy[0][i-1];
  474.                     double dx2=dpx2-bfx[0][i-1],dy2=dpy2-bfy[0][i-1];
  475.                     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;}
  476.                 }
  477.             }
  478.         }
  479.        
  480.     }
  481.    
  482.     // for drawn-in hydrogens, work out which quadrant they are to be drawn on; done by working out angles to bonds, and finding the
  483.     // lowest sum total of distance; note values: 1=east,2=north,3=west,4=south (+N*90 degrees)
  484.     void SetupHydrogens()
  485.     {
  486.         hdir=new int[mol.NumAtoms()];
  487.        
  488.         for (int n=1;n<=mol.NumAtoms();n++)
  489.         {
  490.             hdir[n-1]=0;
  491.             if (hcount[n-1]==0) continue;
  492.             int bonds[]=mol.AtomAdjList(n);
  493.             int avoid1=0,avoid2=20,avoid3=10,avoid4=20;
  494.             for (int i=0;i<bonds.length + (bondInProgress && bipFrom==n ? 1 : 0);i++)
  495.             {
  496.                 double x=i<bonds.length ? mol.AtomX(bonds[i]) : bipToX,y=i<bonds.length ? mol.AtomY(bonds[i]) : bipToY;
  497.                 double theta=Math.atan2(y-mol.AtomY(n),x-mol.AtomX(n))*180/Math.PI;
  498.                 double dt1=0-theta,dt2=90-theta,dt3=180-theta,dt4=270-theta;
  499.                 dt1=Math.abs(dt1+(dt1<-180 ? 360:0)+(dt1>180 ? -360 : 0));
  500.                 dt2=Math.abs(dt2+(dt2<-180 ? 360:0)+(dt2>180 ? -360 : 0));
  501.                 dt3=Math.abs(dt3+(dt3<-180 ? 360:0)+(dt3>180 ? -360 : 0));
  502.                 dt4=Math.abs(dt4+(dt4<-180 ? 360:0)+(dt4>180 ? -360 : 0));
  503.                 avoid1+=dt1<80 ? 80-dt1 : 0;
  504.                 avoid2+=dt2<80 ? 80-dt2 : 0;
  505.                 avoid3+=dt3<80 ? 80-dt3 : 0;
  506.                 avoid4+=dt4<80 ? 80-dt4 : 0;
  507.             }
  508.             if (avoid1<=avoid2 && avoid1<=avoid3 && avoid1<=avoid4) hdir[n-1]=1;
  509.             else if (avoid3<=avoid2 && avoid3<=avoid4) hdir[n-1]=3;
  510.             else if (avoid2<=avoid4) hdir[n-1]=2;
  511.             else hdir[n-1]=4;
  512.         }
  513.     }
  514.    
  515.     // jm.evers 1/2010 select atoms with select_colors & select_atoms via applet param
  516.     void DrawExternalAtomSelection(){
  517.         if(MainApplet.ExternalAtomSelection !=  null){
  518.             for (int n=1;n<=mol.NumAtoms();n++){
  519.                 for(int i=0;i<MainApplet.ExternalAtomSelection.length;i++){
  520.                     if( n == MainApplet.ExternalAtomSelection[i]){
  521.                         g.setColor(MainApplet.SelectedAtomColorArray[i]);
  522.                         double ext=Math.max(rw[n-1]*1.2,rh[n-1]*1.2);
  523.                         g.fill(new Ellipse2D.Double(px[n-1]-ext,py[n-1]-ext,2*ext,2*ext));
  524.                     }
  525.                 }
  526.             }
  527.         }
  528.     }
  529.     // draws various types of highlighting, if appropriate
  530.     void DrawBacklighting()
  531.     {
  532.         for (int n=1;n<=mol.NumAtoms();n++)
  533.         {          
  534.             boolean drag=false;
  535.             if (dragged!=null) drag=dragged[n-1];
  536.            
  537.             // 12/2009 jm.evers fix trouble with invisble loose C atoms...paint loose C's
  538.             // System.out.println(mol.AtomAdjCount(n));
  539.             if( mol.AtomElement(n).equals("C") && viewC == false){
  540.                 if(mol.AtomAdjCount(n) == 0){
  541.                     double ext=Math.max(rw[n-1]*1.2,rh[n-1]*1.2);
  542.                     g.setColor(colDragged);
  543.                     g.fill(new Ellipse2D.Double(px[n-1]-ext,py[n-1]-ext,2*ext,2*ext));
  544.                 }
  545.             }
  546.             if (selected[n-1] || n==highlightAtom || drag)
  547.             {
  548.                 g.setColor(selected[n-1] ? colSelected : drag ? colDragged : colHighlight);
  549.                 double ext=Math.max(rw[n-1]*1.2,rh[n-1]*1.2);
  550.                 g.fill(new Ellipse2D.Double(px[n-1]-ext,py[n-1]-ext,2*ext,2*ext));
  551.             }
  552.         }
  553.  
  554.         if (highlightBond!=0) for (int n=0;n==0 || n<mol.BondOrder(highlightBond) && n<3;n++)
  555.         {
  556.             int bn=highlightBond-1;
  557.             double x1=bfx[n][bn],y1=bfy[n][bn],x2=btx[n][bn],y2=bty[n][bn];
  558.             double dx=x2-x1,dy=y2-y1;
  559.             double norm=0.15*scale/Math.sqrt(dx*dx+dy*dy);
  560.             double ox=norm*dy,oy=-norm*dx;
  561.             Polygon pgn=new Polygon();
  562.             pgn.addPoint((int)Math.round(x1+oy*0.5),(int)Math.round(y1-ox*0.5));
  563.             pgn.addPoint((int)Math.round(x1-ox),(int)Math.round(y1-oy));
  564.             pgn.addPoint((int)Math.round(x2-ox),(int)Math.round(y2-oy));
  565.             pgn.addPoint((int)Math.round(x2-oy*0.5),(int)Math.round(y2+ox*0.5));
  566.             pgn.addPoint((int)Math.round(x2+ox),(int)Math.round(y2+oy));
  567.             pgn.addPoint((int)Math.round(x1+ox),(int)Math.round(y1+oy));
  568.             g.fill(pgn);
  569.         }
  570.     }
  571.  
  572.     // draws all of the bonds
  573.     void DrawBonds(){
  574.         boolean foundselected;
  575.         for (int n=1;n<=mol.NumBonds()+(bondInProgress ? 1 : 0);n++){
  576.             g.setColor(Color.black);
  577.             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;
  578.             int order=n<=mol.NumBonds() ? mol.BondOrder(n) : bipOrder;
  579.             int type=n<=mol.NumBonds() ? mol.BondType(n) : bipType;
  580.             boolean mappedBond=n<=mol.NumBonds() ? (mol.AtomMapNum(mol.BondFrom(n))>0 && mol.AtomMapNum(mol.BondTo(n))>0) : false;
  581.             // jm.evers
  582.             if(MainApplet.user_selection){
  583.                 if( EditorPane.bondselection.length > mol.NumBonds()){ // userbased click selection
  584.                     if( EditorPane.bondselection[n] ){
  585.                         g.setColor(MainApplet.DefaultBondSelectionColor);
  586.                     }
  587.                 }
  588.                 else
  589.                 {
  590.                     EditorPane.bondselection = EditorPane.GrowArray(EditorPane.bondselection,mol.NumBonds()+32);
  591.                 }
  592.             }
  593.             if( MainApplet.ExternalBondSelection != null ){ // for answer molecule ; set by params
  594.                 for(int i=0;i<MainApplet.ExternalBondSelection.length;i++){
  595.                     if( n == MainApplet.ExternalBondSelection[i]){
  596.                         g.setColor(MainApplet.SelectedBondColorArray[i]);
  597.                     }
  598.                 }
  599.             }
  600.             if (type==Molecule.BONDTYPE_INCLINED)
  601.             {  
  602.                 double norm=0.15*scale/Math.sqrt(dx*dx+dy*dy);
  603.                 double ox=norm*dy,oy=-norm*dx;
  604.                 Polygon pgn=new Polygon();
  605.                 pgn.addPoint((int)Math.round(x1),(int)Math.round(y1));
  606.                 pgn.addPoint((int)Math.round(x2-ox),(int)Math.round(y2-oy));
  607.                 pgn.addPoint((int)Math.round(x2+ox),(int)Math.round(y2+oy));
  608.                 g.fill(pgn);
  609.             }
  610.             else if (type==Molecule.BONDTYPE_DECLINED)
  611.             {
  612.                 int nsteps=(int)Math.ceil(Math.sqrt(dx*dx+dy*dy)*0.15);
  613.                 double norm=0.15*scale/Math.sqrt(dx*dx+dy*dy);
  614.                 double ox=norm*dy,oy=-norm*dx;
  615.                 for (int i=0;i<=nsteps+1;i++)
  616.                 {
  617.                     double cx=x1+i*dx/(nsteps+1),cy=y1+i*dy/(nsteps+1);
  618.                     double ix=ox*i/(nsteps+1),iy=oy*i/(nsteps+1);
  619.                     g.setStroke(new BasicStroke((float)(0.05*scale)));
  620.                     g.draw(new Line2D.Double(cx-ix,cy-iy,cx+ix,cy+iy));
  621.                 }
  622.             }
  623.             else if (type==Molecule.BONDTYPE_UNKNOWN)
  624.             {
  625.                 int nsteps=(int)Math.ceil(Math.sqrt(dx*dx+dy*dy)*0.2);
  626.                 double norm=0.2*scale/Math.sqrt(dx*dx+dy*dy);
  627.                 double ox=norm*dy,oy=-norm*dx;
  628.                 for (int i=0;i<=nsteps;i++)
  629.                 {
  630.                     double ax=x1+i*dx/(nsteps+1),ay=y1+i*dy/(nsteps+1);
  631.                     double cx=x1+(i+1)*dx/(nsteps+1),cy=y1+(i+1)*dy/(nsteps+1);
  632.                     double bx=(ax+cx)/2,by=(ay+cy)/2;
  633.                     int sign=i%2==0 ? 1 : -1;
  634.                     g.setStroke(new BasicStroke((float)(0.05*scale)));
  635.                     g.draw(new QuadCurve2D.Double(ax,ay,bx+sign*ox,by+sign*oy,cx,cy));
  636.                 }
  637.             }
  638.             else if (order==0)
  639.             {
  640.                 int nsteps=(int)Math.ceil(Math.sqrt(dx*dx+dy*dy)*0.10);
  641.                 float radius=(float)(mappedBond ? 0.2*scale : 0.1*scale);
  642.                 for (int i=0;i<=nsteps+1;i++)
  643.                 {
  644.                     double cx=x1+i*dx/(nsteps+1),cy=y1+i*dy/(nsteps+1);
  645.                     g.fill(new Ellipse2D.Double(cx-0.05*scale,cy-0.05*scale,radius,radius));
  646.                 }
  647.             }
  648.             else
  649.             {
  650.                 float thickness=(float)(mappedBond ? 0.125*scale : 0.075*scale);
  651.                 g.setStroke(new BasicStroke(thickness,BasicStroke.CAP_ROUND,BasicStroke.JOIN_ROUND));
  652.                 for (int i=order<=3 ? order-1 : 2;i>=0;i--)
  653.                     g.draw(new Line2D.Double(bfx[i][n-1],bfy[i][n-1],btx[i][n-1],bty[i][n-1]));
  654.             }
  655.         }
  656.     }
  657.    
  658.     // draw the atoms proper, as well as standard annotations such as hydrogen atoms and charges
  659.     void DrawAtoms(){
  660.         // atoms first
  661.         for (int n=1;n<=mol.NumAtoms();n++){
  662.             // jm.evers 20/2/2010 show nonvisible
  663.             if (atomLabel[n-1].length()==0 && viewC) continue;
  664.             if( MainApplet.user_selection){
  665.                 if(EditorPane.atomselection.length > mol.NumAtoms() ){//jm.evers
  666.                     if( EditorPane.atomselection[n] ){
  667.                         g.setColor(MainApplet.DefaultAtomSelectionColor);
  668.                         double ext=Math.max(rw[n-1]*1.2,rh[n-1]*1.2);
  669.                         g.fill(new Ellipse2D.Double(px[n-1]-ext,py[n-1]-ext,2*ext,2*ext));
  670.                     }
  671.                 }else{
  672.                     EditorPane.atomselection = EditorPane.GrowArray(EditorPane.atomselection,mol.NumAtoms()+32);
  673.                 }
  674.             }
  675.             g.setColor(showMode==SHOW_INDEXES ? Color.BLUE : showMode==SHOW_RINGID ? Color.RED :
  676.             showMode==SHOW_PRIORITY ? Color.GREEN.darker() : showMode==SHOW_MAPNUM ? Color.MAGENTA : Color.BLACK);
  677.             double hx=px[n-1],hy=py[n-1] -0.1*metrics.getAscent(); // (& little fudge for font alignment)
  678.             g.setFont(mol.AtomMapNum(n)>0 && (showMode==SHOW_ELEMENTS || showMode==SHOW_ALL_ELEMENTS) ? boldFont : font);
  679.             g.drawString(atomLabel[n-1],(float)(hx-rw[n-1]),(float)(hy+0.5*metrics.getAscent()));
  680.             if (hcount[n-1]>0){
  681.                 int bigHWid=metrics.stringWidth("H");
  682.                 int subHWid=hcount[n-1]>1 ? smallMetrics.stringWidth(String.valueOf(hcount[n-1])) : 0;
  683.                 if (hdir[n-1]==1) {hx+=rw[n-1]; hy+=0.5*metrics.getAscent();}
  684.                 else if (hdir[n-1]==3) {hx-=rw[n-1]+bigHWid+subHWid; hy+=0.5*metrics.getAscent();}
  685.                 else if (hdir[n-1]==2) {hx-=rw[n-1]; hy-=0.5*metrics.getAscent();}
  686.                 else if (hdir[n-1]==4) {hx-=rw[n-1]; hy+=1.5*metrics.getAscent();}
  687.                 g.setFont(font);
  688.                 // hydrogen color
  689.                 g.setColor(Color.BLACK);
  690.                 g.drawString("H",(float)hx,(float)hy);
  691.                 if (hcount[n-1]>1) {
  692.                     g.setFont(smallFont);
  693.                     g.drawString(String.valueOf(hcount[n-1]),(float)hx+bigHWid,(float)hy);
  694.                 }
  695.             }
  696.         }
  697.        
  698.         // annotations next
  699.         final int ANNOT_PLUS=1,ANNOT_MINUS=2,ANNOT_RAD=3;
  700.         ArrayList<Integer> annot=new ArrayList<Integer>();
  701.         double usz=scale*0.3;
  702.  
  703.         for (int n=1;n<=mol.NumAtoms();n++)
  704.         {
  705.             annot.clear();
  706.             int chg=mol.AtomCharge(n);
  707.             while (chg<0) {annot.add(ANNOT_MINUS); chg++;}
  708.             while (chg>0) {annot.add(ANNOT_PLUS); chg--;}
  709.             int rad=mol.AtomUnpaired(n);
  710.             while (rad>0) {annot.add(ANNOT_RAD); rad--;}
  711.            
  712.             if (annot.size()==0) continue;
  713.            
  714.             double bw=annot.size()*usz,bh=usz;
  715.             final int ANG_INCR=5,CLOCK_SZ=360/ANG_INCR,SAMP_SZ=3*CLOCK_SZ;
  716.             double bestAng=0,bestExt=0,bestScore=Double.MAX_VALUE;
  717.             for (int i=1;i<=3;i++) for (int j=0;j<CLOCK_SZ;j++)
  718.             {
  719.                 double ang=j*ANG_INCR;
  720.                 if (hdir[n-1]==1 && (ang<=45 || ang>=315)) continue;
  721.                 if (hdir[n-1]==4 && ang>=45 && ang<=135) continue;
  722.                 if (hdir[n-1]==3 && ang>=135 && ang<=225) continue;
  723.                 if (hdir[n-1]==2 && ang>=225 && ang<=315) continue;
  724.                
  725.                 double ext=0.5*(rw[n-1]+rw[n-1])+i*scale*0.25;
  726.                 double from45=Math.min(Math.abs(315-ang + (ang<135 ? -360 : 0)),90);
  727.                 double score=10*ext + 0.01*from45;
  728.                 if (score>bestScore) continue;
  729.        
  730.                 double ax=px[n-1]+ext*Math.cos(ang*Math.PI/180.0);
  731.                 double ay=py[n-1]+ext*Math.sin(ang*Math.PI/180.0);
  732.                
  733.                 for (int k=1;k<=mol.NumBonds();k++)
  734.                 {
  735.                     double dsq=0;
  736.                     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];
  737.                     double vx=x2-x1,vy=y2-y1,wx=ax-x1,wy=ay-y1;
  738.                     double c1=vx*wx+vy*wy;
  739.                     if (c1<=0) dsq=(ax-x1)*(ax-x1)+(ay-y1)*(ay-y1);
  740.                     else
  741.                     {
  742.                         double c2=vx*vx+vy*vy;
  743.                         if (c2<=c1) dsq=(ax-x2)*(ax-x2)+(ay-y2)*(ay-y2);
  744.                         else
  745.                         {
  746.                             double b=c1/c2;
  747.                             double bx=x1+b*vx,by=y1+b*vy;
  748.                             dsq=(ax-bx)*(ax-bx)+(ay-by)*(ay-by);
  749.                         }
  750.                     }
  751.                    
  752.                     score+=100/Math.max(dsq,0.0001);
  753.                 }              
  754.        
  755.                 if (score<bestScore)
  756.                 {
  757.                     bestAng=ang;
  758.                     bestExt=ext;
  759.                     bestScore=score;
  760.                 }
  761.             }
  762.            
  763.             double ax=px[n-1]+bestExt*Math.cos(bestAng*Math.PI/180.0)-0.5*bw;
  764.             double ay=py[n-1]+bestExt*Math.sin(bestAng*Math.PI/180.0)-0.5*bh;
  765.             g.setColor(Color.BLACK);
  766.             for (int i=0;i<annot.size();i++)
  767.             {
  768.                 int type=annot.get(i).intValue();
  769.                 double x1=ax+0.2*usz,x2=ax+0.8*usz,y1=ay+0.2*usz,y2=ay+0.8*usz;
  770.                 if (type==ANNOT_MINUS || type==ANNOT_PLUS)
  771.                 {
  772.                     g.draw(new Line2D.Double(x1,0.5*(y1+y2),x2,0.5*(y1+y2)));
  773.                 }
  774.                 if (type==ANNOT_PLUS)
  775.                 {
  776.                     g.draw(new Line2D.Double(0.5*(x1+x2),y1,0.5*(x1+x2),y2));
  777.                 }
  778.                 if (type==ANNOT_RAD)
  779.                 {
  780.                     g.fill(new Ellipse2D.Double(ax+0.2*usz,ay+0.2*usz,0.6*usz,0.6*usz));
  781.                 }
  782.                 ax+=usz;
  783.             }
  784.         }
  785.     }
  786.    
  787.     // draw the less common annotations, such as stereo labels, atom mapping numbers, etc.
  788.     void DrawAnnotations()
  789.     {
  790.         if (showSter)
  791.         {
  792.             for (int n=1,chi;n<=mol.NumAtoms();n++) if ((chi=mol.AtomChirality(n))!=Molecule.STEREO_NONE)
  793.             {
  794.                 String label=chi==Molecule.STEREO_POS ? "R" : chi==Molecule.STEREO_NEG ? "S" : "R/S";
  795.                 g.setColor(Color.BLUE);
  796.                 g.setFont(font);
  797.                 g.drawString(label,(float)(px[n-1]-0.5*metrics.stringWidth(label)),(float)(py[n-1]+metrics.getHeight()));
  798.             }
  799.             for (int n=1,chi;n<=mol.NumBonds();n++) if ((chi=mol.BondStereo(n))!=Molecule.STEREO_NONE)
  800.             {
  801.                 String label=chi==Molecule.STEREO_POS ? "Z" : chi==Molecule.STEREO_NEG ? "E" : "E/Z";
  802.                 int i1=mol.BondFrom(n)-1,i2=mol.BondTo(n)-1;
  803.                 g.setColor(Color.BLUE);
  804.                 g.setFont(font);
  805.                 g.drawString(label,(float)(0.5*(px[i1]+px[i2]-metrics.stringWidth(label))),
  806.                                    (float)(0.5*(py[i1]+py[i2]+metrics.getHeight())));
  807.             }
  808.         }
  809.     }
  810.  
  811.     // draw the effects of various editing-in-progress actions
  812.     void DrawEffects()
  813.     {
  814.         if (atomInProgress)
  815.         {
  816.             g.setColor(Color.BLUE);
  817.             g.setFont(font);
  818.             double tx=AngToX(aipToX),ty=AngToY(aipToY);
  819.             g.drawString(aipLabel,(float)(tx-0.5*metrics.stringWidth(aipLabel)),(float)(ty+0.4*metrics.getAscent()));
  820.         }
  821.  
  822.         if (newBondLine)
  823.         {
  824.             g.setColor(Color.BLACK);
  825.             g.draw(new Line2D.Double(AngToX(nblX1),AngToY(nblY1),AngToX(nblX2),AngToY(nblY2)));
  826.         }
  827.        
  828.         if (dragSelect)
  829.         {
  830.             g.setXORMode(Color.YELLOW);
  831.             int x=dslX1,y=dslY1,w=dslX2-dslX1,h=dslY2-dslY1;
  832.             if (w<0) {w=-w; x-=w;}
  833.             if (h<0) {h=-h; y-=h;}
  834.             g.drawRect(x,y,w,h);
  835.             g.setXORMode(Color.BLACK);
  836.         }
  837.        
  838.         if (dragScale)
  839.         {
  840.             g.setColor(Color.BLACK);
  841.             g.setStroke(new BasicStroke(1.1F));
  842.             for (int n=1;n<=mol.NumAtoms();n++) if (selected[n-1])
  843.             {
  844.                 double sx=AngToX((mol.AtomX(n)-dscCX)*dscExtMul+dscCX),sy=AngToY((mol.AtomY(n)-dscCY)*dscExtMul+dscCY);
  845.                 g.draw(new Ellipse2D.Double(sx-scale*0.3,sy-scale*0.3,scale*0.6,scale*0.6));
  846.             }
  847.         }
  848.        
  849.         if (dragMove)
  850.         {
  851.             g.setColor(Color.BLACK);
  852.             g.setStroke(new BasicStroke(1.1F));
  853.             for (int n=1;n<=mol.NumAtoms();n++) if (selected[n-1])
  854.             {
  855.                 double sx=px[n-1]+dmvDX,sy=py[n-1]+dmvDY;
  856.                 g.draw(new Ellipse2D.Double(sx-scale*0.3,sy-scale*0.3,scale*0.6,scale*0.6));
  857.                 if (dmvCopy)
  858.                 {
  859.                     g.draw(new Line2D.Double(sx-scale*0.15,sy,sx+scale*0.15,sy));
  860.                     g.draw(new Line2D.Double(sx,sy-scale*0.15,sx,sy+scale*0.15));
  861.                 }
  862.             }
  863.         }
  864.        
  865.         if (dragRotate)
  866.         {
  867.             double thrad=droTheta*Math.PI/180;
  868.             g.setColor(Color.RED);
  869.             g.setStroke(new BasicStroke(0.5F,BasicStroke.CAP_ROUND,BasicStroke.JOIN_ROUND,1F,new float[]{2,2},0));
  870.             g.draw(new Line2D.Double(droX,droY,droX+50,droY));
  871.             g.setStroke(new BasicStroke(1F));
  872.             g.draw(new Line2D.Double(droX,droY,droX+50*Math.cos(-thrad),droY+50*Math.sin(-thrad)));
  873.             g.draw(new Arc2D.Double(droX-20,droY-20,40,40,0,droTheta,Arc2D.OPEN));
  874.  
  875.             int ty=(int)((droTheta>25 || (droTheta<0 && droTheta>=-25)) ? droY-5 : droY+5+txh);
  876.             DecimalFormat fmt=new DecimalFormat("0");
  877.             g.drawString((droTheta>0?"+":"")+fmt.format(Math.round(droTheta)),(int)(droX+25),ty);
  878.            
  879.             double ax=XToAng(droX),ay=YToAng(droY);
  880.             g.setStroke(new BasicStroke(1.1F));
  881.             for (int n=1;n<=mol.NumAtoms();n++) if (selected[n-1])
  882.             {
  883.                 double rx=mol.AtomX(n)-ax,ry=mol.AtomY(n)-ay;
  884.                 double rth=Math.atan2(ry,rx),ext=Math.sqrt(rx*rx+ry*ry);
  885.                 rx=ax+ext*Math.cos(rth+thrad);
  886.                 ry=ay+ext*Math.sin(rth+thrad);
  887.                 g.draw(new Ellipse2D.Double(AngToX(rx)-scale*0.3,AngToY(ry)-scale*0.3,scale*0.6,scale*0.6));
  888.             }
  889.         }
  890.                    
  891.         if (outlineTemplate)
  892.         {
  893.             g.setColor(new Color(128,128,128));
  894.             g.setStroke(new BasicStroke(1));
  895.             for (int n=1;n<=oltMol.NumBonds();n++)
  896.             {
  897.                 int from=oltMol.BondFrom(n),to=oltMol.BondTo(n);
  898.                 g.draw(new Line2D.Double(AngToX(oltMol.AtomX(from)),AngToY(oltMol.AtomY(from)),
  899.                                          AngToX(oltMol.AtomX(to)),AngToY(oltMol.AtomY(to))));
  900.             }
  901.         }
  902.     }
  903.    
  904.     // bring attention to structures which are malformed
  905.     void DrawCorrections()
  906.     {
  907.         // see if any atoms severely overlap, and if so, draw a red circle around them
  908.         for (int i=1;i<=mol.NumAtoms()-1;i++)
  909.         for (int j=i+1;j<=mol.NumAtoms();j++)
  910.         {
  911.             double dx=mol.AtomX(i)-mol.AtomX(j),dy=mol.AtomY(i)-mol.AtomY(j);
  912.             if (dx*dx+dy*dy<0.2*0.2)
  913.             {
  914.                 g.setColor(Color.RED);
  915.                 g.setStroke(new BasicStroke(0.5F));
  916.                 g.draw(new Ellipse2D.Double(px[i-1]-scale*0.25,py[i-1]-scale*0.25,scale*0.5,scale*0.5));
  917.  
  918.             }
  919.         }
  920.     }
  921.    
  922.     // translation of screen & molecule coordinates    
  923.     double AngToX(double AX) {return (offsetX+AX)*scale;}
  924.     double AngToY(double AY) {return (offsetY-AY)*scale;}
  925.     double XToAng(double PX) {return (PX/scale)-offsetX;}
  926.     double YToAng(double PY) {return (-PY/scale)+offsetY;}
  927. }
  928.