Subversion Repositories wimsdev

Rev

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

  1. /*    Sketch Elements: Chemistry molecular diagram drawing tool.
  2.    
  3.     (c) 2008 Dr. Alex M. Clark
  4.    
  5.     Released as GNUware, under the Gnu Public License (GPL)
  6.    
  7.     See www.gnu.org for details.
  8. */
  9.  
  10. package WIMSchem;
  11.  
  12. import java.lang.*;
  13. import java.text.*;
  14. import java.util.*;
  15. import java.awt.geom.*;
  16.  
  17. /*
  18.     Given an input molecule, and pseudo-pixel resolution, and a way to measure text, this class provides the necessary
  19.     arrangements which precede the actual drawing of the molecule.
  20. */
  21.  
  22. public class ArrangeMolecule
  23. {
  24.     private Molecule mol;
  25.     private double scale; // pixels-per-Angstrom, or similar
  26.     private ArrangeMeasurement measure;
  27.  
  28.     public static final int SHOW_ELEMENTS=0;
  29.     public static final int SHOW_ALL_ELEMENTS=1;
  30.     public static final int SHOW_INDEXES=2;
  31.     public static final int SHOW_RINGID=3;
  32.     public static final int SHOW_PRIORITY=4;
  33.     public static final int SHOW_MAPNUM=5;
  34.     public int unselected_color = 0x000000;
  35.     private int elementMode; // one of SHOW_* above
  36.     private boolean showHydrogens; // if false, H's will never be shown
  37.     private double fontSize; // font size for main features, e.g. elements
  38.     private double lineSize; // width of most lines, such as bonds
  39.     private double bondSep; // size of bond separation between non-single bonds
  40.     private boolean devRounding; // if true, sometimes rounds to "pixel" boundaries to improve rendering
  41.     private boolean annotRS=false,annotEZ=false; // optional extra annotations
  42.     class APoint
  43.     {
  44.         int anum; // corresponds to molecule atom index
  45.         String text; // the primary label, or null if invisible
  46.         double fsz; // font size for label
  47.         boolean bold;
  48.         int col;
  49.         double cx,cy,rw,rh; // centre and ovaloid radius for label shape; rw,rh==0 if no label
  50.     }
  51.    
  52.     public static final int BLINE_NORMAL=1; // a line segment; may be single bond, part of a multiple bond, or dissected bond
  53.     public static final int BLINE_INCLINED=2; // an up-wedge bond
  54.     public static final int BLINE_DECLINED=3; // a down-wedge bond
  55.     public static final int BLINE_UNKNOWN=4; // a squiggly bond
  56.     public static final int BLINE_DOTTED=5; // dotted line
  57.    
  58.     class BLine
  59.     {
  60.         int bnum; // corresponds to molecule bond index
  61.         int type; // one of BLINE_*
  62.         double x1,y1,x2,y2;
  63.         double size;
  64.         int col;
  65.        
  66.         BLine() {}
  67.         BLine(int bnum,int type,double x1,double y1,double x2,double y2,double size,int col)
  68.         {
  69.             this.bnum=bnum;
  70.             this.type=type;
  71.             this.x1=x1;
  72.             this.y1=y1;
  73.             this.x2=x2;
  74.             this.y2=y2;
  75.             this.size=size;
  76.             this.col=col;
  77.         }
  78.     }
  79.    
  80.     private ArrayList<APoint> points=new ArrayList<APoint>();
  81.     private ArrayList<BLine> lines=new ArrayList<BLine>();
  82.    
  83.     // constructor: parameters are options which have no reasonable defaults
  84.    
  85.     public ArrangeMolecule(Molecule mol,ArrangeMeasurement measure)
  86.     {
  87.         this.mol=mol;
  88.         this.measure=measure;
  89.        
  90.         scale=measure.scale();
  91.         elementMode=SHOW_ELEMENTS;
  92.         showHydrogens=true;
  93.         fontSize=0.6*scale;
  94.         lineSize=0.075*scale;
  95.         bondSep=0.20*scale;
  96.         devRounding=true;
  97.     }
  98.    
  99.     // methods for using non-default display settings
  100.    
  101.     public void setShowHydrogens(boolean v) {showHydrogens=v;}
  102.     public void setElementMode(int v) {elementMode=v;}
  103.     public void setFontSizeAng(double v) {fontSize=v*scale;}
  104.     public void setFontSizeDev(double v) {fontSize=v;}
  105.     public void setLineSizeAng(double v) {lineSize=v*scale;}
  106.     public void setDevRounding(boolean v) {devRounding=v;}
  107.     public void setAnnotRS(boolean v) {annotRS=v;}
  108.     public void setAnnotEZ(boolean v) {annotEZ=v;}
  109.    
  110.     public boolean getShowHydrogens() {return showHydrogens;}
  111.     public int getElementMode() {return elementMode;}
  112.     public double getFontSizeDev() {return fontSize;}
  113.     public double getLineSizeDev() {return lineSize;}
  114.     public boolean getDevRounding() {return devRounding;}
  115.     public boolean getAnnotRS() {return annotRS;}
  116.     public boolean getAnnotEZ() {return annotEZ;}
  117.  
  118.     // the action method: call this before accessing any of the resultant data
  119.     public void arrange()
  120.     {
  121.         // fill in each of the atom centres
  122.         if( MainApplet.viewC ){elementMode = 1;}
  123.         if( MainApplet.viewH ){ showHydrogens = true;}
  124.         for (int n=1;n<=mol.numAtoms();n++)
  125.         {
  126.             APoint a=new APoint();
  127.             a.anum=n;
  128.             a.fsz=fontSize;
  129.             a.bold=mol.atomMapNum(n)>0;
  130.            
  131.             a.col=unselected_color;
  132.             if(MainApplet.USER_SELECTION){/*give the molecule the user selected colours */
  133.                 if(EditorPane.atomselection[n]){a.col = MainApplet.ATOM_SELECT_HTML_COLOR;}
  134.             }
  135.             else
  136.             {
  137.                 if(MainApplet.SUPERUSER_SELECTION){/*give the molecule the param selected colours */
  138.                     if( MainApplet.ExternalAtomSelection != null ){
  139.                         for(int p=0 ; p < (MainApplet.ExternalAtomSelection).length; p++ ){
  140.                             if( n == MainApplet.ExternalAtomSelection[p] ){
  141.                                 a.col = MainApplet.SelectedAtomColorInt[p] ;
  142.                             }
  143.                         }
  144.                     }
  145.                 }
  146.             }
  147.             a.cx=measure.angToX(mol.atomX(n));
  148.             a.cy=measure.angToY(mol.atomY(n));
  149.             a.rw=a.rh=0;
  150.            
  151.             if (devRounding)
  152.             {
  153.                 a.cx=Math.round(a.cx);
  154.                 a.cy=Math.round(a.cy);
  155.             }
  156.             // decide whether this atom is to have a label
  157.             switch(elementMode){
  158.              case SHOW_ELEMENTS: a.text=mol.atomExplicit(n) ? mol.atomElement(n) : null; break;
  159.              case SHOW_ALL_ELEMENTS: a.text=mol.atomElement(n); break;
  160.              case SHOW_INDEXES: a.text=String.valueOf(n);break;
  161.              case SHOW_RINGID: a.text=String.valueOf(mol.atomRingBlock(n));break;
  162.              case SHOW_PRIORITY: a.text=String.valueOf(mol.atomPriority(n));break;
  163.              case SHOW_MAPNUM: a.text=mol.atomMapNum(n)>0 ? String.valueOf(mol.atomMapNum(n)) : "";break;
  164.              default: a.text="?";break;
  165.             }
  166.             // if it has a label, then how big
  167.             if (a.text!=null)
  168.             {
  169.                 double[] wad=measure.measureText(a.text,a.fsz);
  170.                 a.rw=0.5*wad[0];
  171.                 a.rh=0.5*wad[1];
  172.             }
  173.  
  174.             points.add(a);
  175.         }
  176.        
  177.         // resolve the bonds which can be analyzed immediately
  178.         boolean[] bdbl=new boolean[mol.numBonds()]; // set to true if bond is awaiting a double bond assignment
  179.         int scol; /* select color */
  180.         for (int n=1;n<=mol.numBonds();n++)
  181.         {
  182.             bdbl[n-1]=mol.bondOrder(n)==2;
  183.             double x1=pointCX(mol.bondFrom(n)-1),y1=pointCY(mol.bondFrom(n)-1);
  184.             double x2=pointCX(mol.bondTo(n)-1),y2=pointCY(mol.bondTo(n)-1);
  185.             scol = unselected_color;       
  186.             if(MainApplet.USER_SELECTION){
  187.                 if(EditorPane.bondselection[n]){
  188.                     scol = MainApplet.BOND_SELECT_HTML_COLOR;
  189.                 }
  190.             }
  191.             else
  192.             {
  193.                 if(MainApplet.SUPERUSER_SELECTION){/*give the molecule the param selected colours */
  194.                     if( MainApplet.ExternalBondSelection != null ){
  195.                         for(int p = 0 ; p < (MainApplet.ExternalBondSelection).length; p++ ){
  196.                             if( n == MainApplet.ExternalBondSelection[p] ){
  197.                                 scol = MainApplet.SelectedBondColorInt[p] ;
  198.                             }
  199.                         }
  200.                     }
  201.                 }
  202.             }
  203.             // for non-double bonds, can add the constituents right away
  204.             if (mol.bondOrder(n)!=2)
  205.             {
  206.                 double[] xy1=backOffAtom(mol.bondFrom(n)-1,x1,y1,x2,y2);
  207.                 double[] xy2=backOffAtom(mol.bondTo(n)-1,x2,y2,x1,y1);
  208.                 double sz=lineSize;
  209.                 if (mol.atomMapNum(mol.bondFrom(n))>0 && mol.atomMapNum(mol.bondTo(n))>0) sz*=5.0/3;
  210.  
  211.                 int ltype=BLINE_NORMAL;
  212.                 if (mol.bondType(n)==Molecule.BONDTYPE_INCLINED) ltype=BLINE_INCLINED;
  213.                 else if (mol.bondType(n)==Molecule.BONDTYPE_DECLINED) ltype=BLINE_DECLINED;
  214.                 else if (mol.bondType(n)==Molecule.BONDTYPE_UNKNOWN) ltype=BLINE_UNKNOWN;
  215.                 else if (mol.bondOrder(n)<=0) ltype=BLINE_DOTTED;
  216.  
  217.                 int bo=mol.bondOrder(n);
  218.  
  219.                 // for dotted lines, back off intersections if non-terminal
  220.                 if (bo==0)
  221.                 {
  222.                     double dx=xy2[0]-xy1[0],dy=xy2[1]-xy1[1],d=Math.sqrt(dx*dx+dy*dy);
  223.                     double ox=dx/d*bondSep,oy=dy/d*bondSep;
  224.                     if (mol.atomAdjCount(mol.bondFrom(n))>1) {xy1[0]+=ox; xy1[1]+=oy;}
  225.                     if (mol.atomAdjCount(mol.bondTo(n))>1) {xy2[0]-=ox; xy2[1]-=oy;}
  226.                 }
  227.  
  228.                 // add one, or several lines
  229.                 if (bo<=1 || mol.bondType(n)!=Molecule.BONDTYPE_NORMAL)
  230.                 {
  231.                     // just one line
  232.                     lines.add(new BLine(n,ltype,xy1[0],xy1[1],xy2[0],xy2[1],sz,scol));
  233.                 }
  234.                 else
  235.                 {
  236.                     double[] oxy=orthogonalDelta(xy1[0],xy1[1],xy2[0],xy2[1],bondSep);
  237.                     double v=-0.5*(bo-1);
  238.                     for (int i=0;i<bo;i++,v++)
  239.                     {
  240.                         lines.add(new BLine(n,BLINE_NORMAL,xy1[0]+v*oxy[0],xy1[1]+v*oxy[1],xy2[0]+v*oxy[0],xy2[1]+v*oxy[1],sz,scol));
  241.                     }
  242.                 }
  243.             }
  244.         }
  245.        
  246.         // process double bonds in rings
  247.         int[][] rings=orderedRingList();
  248.         for (int i=0;i<rings.length;i++)
  249.         {
  250.             for (int j=0;j<rings[i].length;j++)
  251.             {
  252.                 int k=mol.findBond(rings[i][j],rings[i][j<rings[i].length-1 ? j+1 : 0]);
  253.                 if (bdbl[k-1])
  254.                 {
  255.                     processDoubleBond(k,rings[i]);
  256.                     bdbl[k-1]=false;
  257.                 }
  258.             }
  259.         }
  260.        
  261.         // process all remaining double bonds
  262.         for (int i=1;i<=mol.numBonds();i++) if (bdbl[i-1]) processDoubleBond(i,priorityDoubleSubstit(i));
  263.        
  264.         // place hydrogen labels as explicit "atom centres"
  265.         int[] hcount=new int[mol.numAtoms()];
  266.         for (int n=1;n<=mol.numAtoms();n++) hcount[n-1]=pointText(n-1)==null || !showHydrogens ? 0 : mol.atomHydrogens(n);
  267.         for (int n=0;n<mol.numAtoms();n++) if (hcount[n]>0)
  268.         {
  269.             if (quadrantOpen(n,1,0) && placeHydrogen(n,hcount[n],1,0)) {hcount[n]=0; continue;}
  270.             if (quadrantOpen(n,-1,0) && placeHydrogen(n,hcount[n],-1,0)) {hcount[n]=0; continue;}
  271.             if (quadrantOpen(n,0,1) && placeHydrogen(n,hcount[n],0,1)) {hcount[n]=0; continue;}
  272.             if (quadrantOpen(n,0,-1) && placeHydrogen(n,hcount[n],0,-1)) {hcount[n]=0; continue;}
  273.         }
  274.         for (int n=0;n<mol.numAtoms();n++) if (hcount[n]>0 && placeHydrogen(n,hcount[n],1,0)) hcount[n]=0;
  275.         for (int n=0;n<mol.numAtoms();n++) if (hcount[n]>0 && placeHydrogen(n,hcount[n],-1,0)) hcount[n]=0;
  276.         for (int n=0;n<mol.numAtoms();n++) if (hcount[n]>0 && placeHydrogen(n,hcount[n],0,1)) hcount[n]=0;
  277.         for (int n=0;n<mol.numAtoms();n++) if (hcount[n]>0 && placeHydrogen(n,hcount[n],0,-1)) hcount[n]=0;
  278.         for (int n=0;n<mol.numAtoms();n++) if (hcount[n]>0) placeHydrogen(n,hcount[n],0,0);
  279.        
  280.         // now do atomic charges/radical notation
  281.         for (int n=1;n<=mol.numAtoms();n++){
  282.             scol = unselected_color;
  283.             if(MainApplet.USER_SELECTION){
  284.                 if(EditorPane.atomselection[n]){
  285.                     scol = MainApplet.ATOM_SELECT_HTML_COLOR;
  286.                 }
  287.             }
  288.             else
  289.             {
  290.                 if(MainApplet.SUPERUSER_SELECTION){/*give the molecule the param selected colours */
  291.                     if( MainApplet.ExternalAtomSelection != null ){
  292.                         for(int p = 0 ; p < (MainApplet.ExternalAtomSelection).length; p++ ){
  293.                             if( n == MainApplet.ExternalAtomSelection[p] ){
  294.                                 scol = MainApplet.SelectedAtomColorInt[p] ;
  295.                             }
  296.                         }
  297.                     }
  298.                 }
  299.             }
  300.             String str="";
  301.             int chg=mol.atomCharge(n);
  302.             if (chg==-1) str="-";
  303.             else if (chg==1) str="+";
  304.             else if (chg<-1) str=String.valueOf(chg);
  305.             else if (chg>1) str="+"+String.valueOf(chg);
  306.             for (int i=mol.atomUnpaired(n);i>0;i--) str+=".";
  307.             if (str.length()==0) continue;
  308.             annotateAtom(n,str,str.length()==1 ? fontSize : 0.75*fontSize,scol);
  309.         }
  310.        
  311.         // add stereo annotations, if requested
  312.         if (annotRS) for (int n=1,chi;n<=mol.numAtoms();n++)
  313.             if ((chi=mol.atomChirality(n))!=Molecule.STEREO_NONE)
  314.         {
  315.             String label=chi==Molecule.STEREO_POS ? "R" : chi==Molecule.STEREO_NEG ? "S" : "R/S";
  316.             annotateAtom(n,label,0,0x0000FF);
  317.         }
  318.         if (annotEZ) for (int n=1,chi;n<=mol.numBonds();n++)
  319.             if ((chi=mol.bondStereo(n))!=Molecule.STEREO_NONE)
  320.         {
  321.             String label=chi==Molecule.STEREO_POS ? "Z" : chi==Molecule.STEREO_NEG ? "E" : "E/Z";
  322.             annotateBond(n,label,0,0x0000FF);
  323.         }
  324.     }
  325.  
  326.     // adds a label to the given atom index, which is drawn near to, but not on top of, the atom itself; font defaults to atom size
  327.     public void annotateAtom(int anum,String label,double fsz,int col)
  328.     {
  329.         if (fsz==0) fsz=fontSize;
  330.         double[] wad=measure.measureText(label,fsz);
  331.         double rw=0.5*wad[0],rh=0.5*wad[1];
  332.         double[] pxy=anchorAnnotation(pointCX(anum-1),pointCY(anum-1),rw,rh);
  333.  
  334.         APoint a=new APoint();
  335.         a.anum=0;
  336.         a.text=label;
  337.         a.fsz=fsz;
  338.         a.bold=false;
  339.         a.col=col;
  340.         a.cx=pxy[0];
  341.         a.cy=pxy[1];
  342.         a.rw=rw;
  343.         a.rh=rh;
  344.         points.add(a);
  345.     }
  346.    
  347.     // adds a label close to the centre of a bond
  348.     public void annotateBond(int bnum,String label,double fsz,int col)
  349.     {
  350.         if (fsz==0) fsz=fontSize;
  351.         double[] wad=measure.measureText(label,fsz);
  352.         double rw=0.5*wad[0],rh=0.5*wad[1];
  353.         int bfr=mol.bondFrom(bnum),bto=mol.bondTo(bnum);
  354.         double[] pxy=anchorAnnotation(0.5*(pointCX(bfr-1)+pointCX(bto-1)),0.5*(pointCY(bfr-1)+pointCY(bto-1)),rw,rh);
  355.  
  356.         APoint a=new APoint();
  357.         a.anum=0;
  358.         a.text=label;
  359.         a.fsz=fsz;
  360.         a.bold=false;
  361.         a.col=col;
  362.         a.cx=pxy[0];
  363.         a.cy=pxy[1];
  364.         a.rw=rw;
  365.         a.rh=rh;
  366.         points.add(a);
  367.     }
  368.    
  369.     // access to atom information; it is valid to assume that {atomcentre}[N-1] matches {moleculeatom}[N], if N<=mol.numAtoms()
  370.     public int numPoints() {return points.size();}
  371.     public int pointANum(int N) {return points.get(N).anum;}
  372.     public String pointText(int N) {return points.get(N).text;}
  373.     public double pointFontSize(int N) {return points.get(N).fsz;}
  374.     public boolean pointBold(int N) {return points.get(N).bold;}
  375.     public int pointCol(int N) {return points.get(N).col;}
  376.     public double pointCX(int N) {return points.get(N).cx;}
  377.     public double pointCY(int N) {return points.get(N).cy;}
  378.     public double pointRW(int N) {return points.get(N).rw;}
  379.     public double pointRH(int N) {return points.get(N).rh;}
  380.    
  381.     // access to bond information; it is NOT valid to read anything into the indices
  382.     public int numLines() {return lines.size();}
  383.     public int lineBNum(int N) {return lines.get(N).bnum;}
  384.     public int lineType(int N) {return lines.get(N).type;}
  385.     public double lineX1(int N) {return lines.get(N).x1;}
  386.     public double lineY1(int N) {return lines.get(N).y1;}
  387.     public double lineX2(int N) {return lines.get(N).x2;}
  388.     public double lineY2(int N) {return lines.get(N).y2;}
  389.     public double lineSize(int N) {return lines.get(N).size;}
  390.     public int lineCol(int N) {return lines.get(N).col;}
  391.    
  392.     // private methods
  393.    
  394.     // given that the position (X,Y) connects with atom N, and is part of a bond that connects at the other end
  395.     // with (FX,FY), considers the possibility that a new (X,Y) may need to be calculated by backing up along the line
  396.     private double[] backOffAtom(int N,double X,double Y,double FX,double FY)
  397.     {
  398.         final double EXT_RAD=1.4;
  399.         double cx=pointCX(N),cy=pointCY(N),rw=pointRW(N)*EXT_RAD,rh=pointRH(N)*EXT_RAD;
  400.         if (pointText(N)==null || rw<=0 || rh<=0) return new double[]{X,Y};
  401.        
  402.         double dx=X-cx,dy=Y-cy;
  403.         double sqX=Util.sqr(dx)/Util.sqr(rw),sqY=Util.sqr(dy)/Util.sqr(rh);
  404.         if (sqX+sqY>=1) return new double[]{X,Y}; // does not intrude on the ellipse
  405.        
  406.         // NOTE: this is slightly wrong because it assumes that lines point toward the centre, which is almost true...
  407.        
  408.         dx=FX-cx; dy=FY-cy;
  409.         sqX=Util.sqr(dx)/Util.sqr(rw); sqY=Util.sqr(dy)/Util.sqr(rh);
  410.         double d=Math.sqrt(1/(sqX+sqY));
  411.         return new double[]{X+d*(FX-X),Y+d*(FY-Y)};
  412.     }
  413.  
  414.     // produces a list of small rings, ordered in a terminal-first manner, which is to be used as the sequence for assigning sides of
  415.     // bond orders
  416.     private int[][] orderedRingList()
  417.     {
  418.         ArrayList<int[]> rings=new ArrayList<int[]>();
  419.         final int[] SIZE_ORDER={6,5,7,4,3};
  420.         for (int i=0;i<SIZE_ORDER.length;i++)
  421.         {
  422.             int[][] nring=mol.findRingSize(SIZE_ORDER[i]);
  423.             for (int j=0;j<nring.length;j++) rings.add(nring[j]);
  424.         }
  425.        
  426.         // keep adding terminal rings to the result set - or, the first ring if none
  427.         ArrayList<int[]> result=new ArrayList<int[]>();
  428.         while (rings.size()>0)
  429.         {
  430.             int which=0; // (default to first)
  431.            
  432.             for (int i=0;i<rings.size();i++)
  433.             {
  434.                 int ncon=0;
  435.                 for (int j=0;j<rings.size();j++) if (i!=j)
  436.                 {
  437.                     boolean shared=false;
  438.                     int[] r1=rings.get(i),r2=rings.get(j);
  439.                     for (int k1=0;k1<r1.length && !shared;k1++) for (int k2=0;k2<r2.length;k2++)
  440.                         if (r1[k1]==r2[k2]) {shared=true; break;}
  441.                     if (shared) ncon++;
  442.                 }
  443.                
  444.                 if (ncon<=1) {which=i; break;}
  445.             }
  446.            
  447.             result.add(rings.get(which));
  448.             rings.remove(which);
  449.         }
  450.        
  451.         //for (int n=0;n<result.size();n++) Util.writeln(Util.arrayStr(result.get(n)));
  452.        
  453.         return result.toArray(new int[result.size()][]);
  454.     }
  455.  
  456.     // convenience function which returns {ox,oy} which is orthogonal to the direction of the input vector, of magnitude D; the direction
  457.     // of {ox,oy} is to the "left" of the input vector
  458.     private double[] orthogonalDelta(double X1,double Y1,double X2,double Y2,double D)
  459.     {
  460.         double ox=Y1-Y2,oy=X2-X1;
  461.         double dsq=ox*ox+oy*oy;
  462.         double sc=dsq>0 ? D/Math.sqrt(dsq) : 1;
  463.         return new double[]{ox*sc,oy*sc};
  464.     }
  465.  
  466.     // given the guideline index of a double bond, and some information about the atoms which are on the "important side", creates the
  467.     // appropriate line segments
  468.     private void processDoubleBond(int N,int[] Priority)
  469.     {
  470.         int bf=mol.bondFrom(N),bt=mol.bondTo(N);
  471.         int[] nf=mol.atomAdjList(bf),nt=mol.atomAdjList(bt);
  472.  
  473.         double x1=pointCX(bf-1),y1=pointCY(bf-1),x2=pointCX(bt-1),y2=pointCY(bt-1);
  474.         double dx=x2-x1,dy=y2-y1,btheta=Math.atan2(dy,dx);
  475.        
  476.         // count number of priority atoms on either side of the bond vector
  477.         int countFLeft=0,countFRight=0,countTLeft=0,countTRight=0;
  478.         int idxFLeft=0,idxFRight=0,idxTLeft=0,idxTRight=0;
  479.         boolean noshift=false; // true if definitely not alkene-ish
  480.         for (int n=0;n<nf.length;n++) if (nf[n]!=bt)
  481.         {
  482.             if (mol.bondOrder(mol.findBond(bf,nf[n]))>1) {noshift=true; break;}
  483.             boolean ispri=false;
  484.             for (int i=0;i<(Priority==null ? 0 : Priority.length);i++) if (Priority[i]==nf[n]) ispri=true;
  485.             double theta=Util.angleDiff(Math.atan2(pointCY(nf[n]-1)-y1,pointCX(nf[n]-1)-x1),btheta);
  486.             if (theta>0) {if (ispri) countFLeft++; idxFLeft=nf[n];}
  487.             else {if (ispri) countFRight++; idxFRight=nf[n];}
  488.         }
  489.         for (int n=0;n<nt.length;n++) if (nt[n]!=bf)
  490.         {
  491.             if (mol.bondOrder(mol.findBond(bt,nt[n]))>1) {noshift=true; break;}
  492.             boolean ispri=false;
  493.             for (int i=0;i<(Priority==null ? 0 : Priority.length);i++) if (Priority[i]==nt[n]) ispri=true;
  494.             double theta=Util.angleDiff(Math.atan2(pointCY(nt[n]-1)-y2,pointCX(nt[n]-1)-x2),btheta);
  495.             if (theta>0) {if (ispri) countTLeft++; idxTLeft=nt[n];}
  496.             else {if (ispri) countTRight++; idxTRight=nt[n];}
  497.         }
  498.        
  499.         // decide which side the bond should be shifted to, if either
  500.         int side=0;
  501.         if (noshift || countFLeft>1 || countFRight>1 || countTLeft>1 || countTRight>1) {} // inappropriate
  502.         else if (countFLeft>0 && countFRight>0) {} // ambiguous
  503.         else if (countTLeft>0 && countTRight>0) {} // ambiguous
  504.         else if (countFLeft>0 || countTLeft>0) side=1; // left
  505.         else if (countFRight>0 || countTRight>0) side=-1; // right
  506.  
  507.         // create the bond lines
  508.  
  509.         double sz=lineSize;
  510.         if (mol.atomMapNum(bf)>0 && mol.atomMapNum(bt)>0) sz*=5.0/3;
  511.         double[] oxy=orthogonalDelta(x1,y1,x2,y2,bondSep);
  512.        
  513.  
  514.         double ax1=x1,ay1=y1,ax2=x2,ay2=y2;
  515.         double bx1=0,by1=0,bx2=0,by2=0;
  516.  
  517.         if (side==0)
  518.         {
  519.             ax1=x1+0.5*oxy[0]; ay1=y1+0.5*oxy[1]; ax2=x2+0.5*oxy[0]; ay2=y2+0.5*oxy[1];
  520.             bx1=x1-0.5*oxy[0]; by1=y1-0.5*oxy[1]; bx2=x2-0.5*oxy[0]; by2=y2-0.5*oxy[1];
  521.         }
  522.         else if (side>0)
  523.         {
  524.             bx1=x1+oxy[0]; by1=y1+oxy[1]; bx2=x2+oxy[0]; by2=y2+oxy[1];
  525.             if (nf.length>1 && pointText(bf-1)==null) {bx1+=oxy[1]; by1-=oxy[0];}
  526.             if (nt.length>1 && pointText(bt-1)==null) {bx2-=oxy[1]; by2+=oxy[0];}
  527.         }
  528.         else if (side<0)
  529.         {
  530.             bx1=x1-oxy[0]; by1=y1-oxy[1]; bx2=x2-oxy[0]; by2=y2-oxy[1];
  531.             if (nf.length>1 && pointText(bf-1)==null) {bx1+=oxy[1]; by1-=oxy[0];}
  532.             if (nt.length>1 && pointText(bt-1)==null) {bx2-=oxy[1]; by2+=oxy[0];}
  533.         }
  534.        
  535.         double[] xy1=backOffAtom(bf-1,ax1,ay1,ax2,ay2);
  536.         double[] xy2=backOffAtom(bt-1,ax2,ay2,ax1,ay1);
  537.         ax1=xy1[0]; ay1=xy1[1]; ax2=xy2[0]; ay2=xy2[1];
  538.  
  539.         xy1=backOffAtom(bf-1,bx1,by1,bx2,by2);
  540.         xy2=backOffAtom(bt-1,bx2,by2,bx1,by1);
  541.         bx1=xy1[0]; by1=xy1[1]; bx2=xy2[0]; by2=xy2[1];
  542.  
  543.         if (devRounding)
  544.         {
  545.             // round the positions, then look for pixel-sized adjustments to B to find the best offset (if any) which honours the two lines
  546.             // being parallel with distance of 'bondSep'
  547.            
  548.             ax1=Math.round(ax1); ay1=Math.round(ay1); ax2=Math.round(ax2); ay2=Math.round(ay2);
  549.             bx1=Math.round(bx1); by1=Math.round(by1); bx2=Math.round(bx2); by2=Math.round(by2);
  550.            
  551.             int dx1=0,dy1=0,dx2=0,dy2=0;
  552.             double best=1E10,bondSepSq=Util.sqr(bondSep);
  553.             final int[] RDX1={0,-1,1,0,0,0,0,0,0},RDY1={0,0,0,-1,1,0,0,0,0},RDX2={0,0,0,0,0,-1,1,0,0},RDY2={0,0,0,0,0,0,0,-1,1};
  554.             boolean orthog=(Util.dblEqual(ax1,ax2) && Util.dblEqual(bx1,bx2)) ||
  555.                            (Util.dblEqual(ay1,ay2) && Util.dblEqual(by1,by2));
  556.             if (!orthog) for (int n=0;n<9;n++)
  557.             {
  558.                 double ux1=bx1+RDX1[n],uy1=by1+RDY1[n],ux2=bx2+RDX2[n],uy2=by2+RDY2[n];
  559.                 double ox=uy1-uy2,oy=ux2-ux1;
  560.                 double[] ixy1=lineIntersection(ux1,uy1,ux1+ox,uy1+oy,ax1,ay1,ax2,ay2);
  561.                 double[] ixy2=lineIntersection(ux2,uy2,ux2+ox,uy2+oy,ax1,ay1,ax2,ay2);
  562.                 double dsq1=Util.sqr(ixy1[0]-ux1)+Util.sqr(ixy1[1]-uy1);
  563.                 double dsq2=Util.sqr(ixy2[0]-ux2)+Util.sqr(ixy2[1]-uy2);
  564.                 double score=Math.abs(dsq1-bondSepSq)+Math.abs(dsq2-bondSepSq);
  565.                 if (score<best)
  566.                 {
  567.                     best=score;
  568.                     dx1=RDX1[n];
  569.                     dy1=RDY1[n];
  570.                     dx2=RDX2[n];
  571.                     dy2=RDY2[n];
  572.                 }
  573.             }
  574.             bx1+=dx1;
  575.             by1+=dy1;
  576.             bx2+=dx2;
  577.             by2+=dy2;
  578.         }
  579.         int scol = unselected_color;
  580.         if(MainApplet.USER_SELECTION){
  581.             if(EditorPane.bondselection[N]){
  582.                 scol = MainApplet.BOND_SELECT_HTML_COLOR;
  583.             }
  584.         }
  585.         else
  586.         {
  587.             if(MainApplet.SUPERUSER_SELECTION){/*give the molecule the param selected colours */
  588.                 if( MainApplet.ExternalBondSelection != null ){
  589.                     for(int p = 0 ; p < (MainApplet.ExternalBondSelection).length; p++ ){
  590.                         if( N == MainApplet.ExternalBondSelection[p] ){
  591.                             scol = MainApplet.SelectedBondColorInt[p] ;
  592.                         }
  593.                     }
  594.                 }
  595.             }
  596.         }
  597.         lines.add(new BLine(N,BLINE_NORMAL,ax1,ay1,ax2,ay2,sz,scol));
  598.         lines.add(new BLine(N,BLINE_NORMAL,bx1,by1,bx2,by2,sz,scol));
  599.  
  600.         if (side==0 && !noshift && mol.atomRingBlock(bf)==0 && mol.atomRingBlock(bt)==0)
  601.         {
  602.             if (pointText(bf-1)==null)
  603.             {
  604.                 adjustBondPosition(idxFLeft,bf,ax1,ay1);
  605.                 adjustBondPosition(idxFRight,bf,bx1,by1);
  606.             }
  607.             if (pointText(bt-1)==null)
  608.             {
  609.                 adjustBondPosition(idxTLeft,bt,ax2,ay2);
  610.                 adjustBondPosition(idxTRight,bt,bx2,by2);
  611.             }
  612.         }
  613.     }
  614.  
  615.     // returns true if, for an atom, all of its neighbours are not oriented in the given directory (180deg)
  616.     private boolean quadrantOpen(int N,int DX,int DY)
  617.     {
  618.         int[] nbr=mol.atomAdjList(N+1);
  619.         for (int n=0;n<nbr.length;n++)
  620.         {
  621.             double ox=pointCX(nbr[n]-1)-pointCX(N),oy=pointCY(nbr[n]-1)-pointCY(N);
  622.             if (DX==1 && DY==0 && ox>0) return false;
  623.             if (DX==-1 && DY==0 && ox<0) return false;
  624.             if (DX==0 && DY==1 && oy>0) return false;
  625.             if (DX==0 && DY==-1 && oy<0) return false;
  626.         }
  627.         return true;
  628.     }
  629.  
  630.     // for an atom centre, with a non-zero number of hydrogen labels required, in the direction indicated by (DX,DY), see if there is room
  631.     // to fit the label, and if there is, stick it there and return true; special case: if (DX,DY) are both zero, then search around the
  632.     // central position looking for the least bad position, and place it there; returns true if the label was placed
  633.     private boolean placeHydrogen(int N,int HCount,int DX,int DY)
  634.     {
  635.         final double KERN_CONST=1.1;
  636.    
  637.         APoint a=points.get(N);
  638.    
  639.         double[] wad=measure.measureText("H",a.fsz);
  640.        
  641.         double cx=a.cx+DX*(a.rw+0.5*wad[0])*KERN_CONST;
  642.         double cy=a.cy+DY*(a.rh+0.5*(wad[1]+wad[2]))*KERN_CONST;
  643.         double rw=0.5*wad[0],rh=0.5*wad[1];
  644.        
  645.         String nstr=HCount>1 ? String.valueOf(HCount) : null;
  646.         double nsz=0.5*a.fsz;
  647.         double[] nwad=nstr==null ? null : measure.measureText(nstr,nsz);
  648.         double nx=0,ny=0,nw=0,nh=0;
  649.         if (nstr!=null)
  650.         {
  651.             nw=0.5*nwad[0];
  652.             nh=0.5*nwad[1];
  653.         }
  654.  
  655.         // if this isn't fallback mode, stop if the position isn't free
  656.         if (DX!=0 || DY!=0)
  657.         {
  658.             if (boxOverlaps(cx-rw,cy-rh,2*rw,2*rh)) return false;
  659.             if (nstr!=null && DX==-1 && DY==0) cx-=nwad[0]*KERN_CONST;
  660.         }
  661.         else
  662.         {
  663.             double bestX=0,bestY=0,bestScore=0;
  664.             for (double th=0;th<2*Math.PI;th+=Math.PI/36) // 5 degree increments
  665.             {
  666.                 double tx=a.cx+a.rw*Math.cos(th),ty=a.cy+a.rh*Math.sin(th);
  667.                 if (tx>a.cx && tx-rw<a.cx+a.rw) tx=a.cx+a.rw+rw;
  668.                 if (tx<a.cx && tx+rw+nw*3>a.cx-a.rw) tx=a.cx-a.rw-rw-nw*2*KERN_CONST;
  669.                 if (ty>a.cy && ty-rh<a.cy+a.rh) ty=a.cy+a.rw+rw;
  670.                 if (ty<a.cy && ty+rh>a.cy-a.rh) ty=a.cy-a.rh-rh;
  671.                 double score=spatialCongestion(tx,ty)+(boxOverlaps(tx-rw,ty-rh,2*rw,2*rh) ? 1000 : 0);
  672.                 if (th==0 || score<bestScore) {bestScore=score; bestX=tx; bestY=ty;}
  673.             }
  674.             cx=bestX;
  675.             cy=bestY;
  676.         }
  677.  
  678.         if (nstr!=null)
  679.         {
  680.             nx=cx+rw+0.75*nwad[0];
  681.             ny=cy+0.75*rh;
  682.         }
  683.        
  684.         APoint ah=new APoint();
  685.         ah.anum=0;
  686.         ah.text="H";
  687.         ah.fsz=a.fsz;
  688.         ah.bold=a.bold;
  689.         ah.col=a.col;
  690.         ah.cx=cx;
  691.         ah.cy=cy;
  692.         ah.rw=rw;
  693.         ah.rh=rh;
  694.         points.add(ah);
  695.        
  696.         if (nstr!=null)
  697.         {
  698.             APoint an=new APoint();
  699.             an.anum=0;
  700.             an.text=nstr;
  701.             an.fsz=0.5*a.fsz;
  702.             an.bold=a.bold;
  703.             an.col=a.col;
  704.             an.cx=nx;
  705.             an.cy=ny;
  706.             an.rw=nw;
  707.             an.rh=nh;
  708.             points.add(an);
  709.         }
  710.        
  711.         return true;
  712.     }
  713.  
  714.     // considering a single-bond line from (bf,bt), make sure the endpoint (indicated by 'bt') is moved to the position (x,y)
  715.     private void adjustBondPosition(int bf,int bt,double x,double y)
  716.     {
  717.         for (int n=0;n<numLines();n++)
  718.         {
  719.             BLine b=lines.get(n);
  720.             if (mol.bondOrder(b.bnum)!=1 && mol.bondType(b.bnum)!=Molecule.BONDTYPE_NORMAL) continue;
  721.  
  722.             if (mol.bondFrom(b.bnum)==bf && mol.bondTo(b.bnum)==bt) {b.x2=x; b.y2=y;}
  723.             if (mol.bondFrom(b.bnum)==bt && mol.bondTo(b.bnum)==bf) {b.x1=x; b.y1=y;}
  724.         }
  725.     }
  726.  
  727.     // for the guideline index of a double bond, determines which side has weighting priority for the drawing of the bond; assumes a chain-
  728.     // like bond (though it could still be in a large ring); a null/empty/ambiguous set implies that there is no priority, and that the bond
  729.     // should not be drawn in a side-shifted manner...
  730.     private int[] priorityDoubleSubstit(int N)
  731.     {
  732.         int bf=mol.bondFrom(N),bt=mol.bondTo(N);
  733.         int[] nf=mol.atomAdjList(bf),nt=mol.atomAdjList(bt);
  734.         double x1=pointCX(bf-1),y1=pointCY(bf-1),x2=pointCX(bt-1),y2=pointCY(bt-1);
  735.         double dx=x2-x1,dy=y2-y1,btheta=Math.atan2(dy,dx);
  736.  
  737.         int idxFLeft=0,idxFRight=0,idxTLeft=0,idxTRight=0;
  738.  
  739.         for (int n=0;n<nf.length;n++) if (nf[n]!=bt)
  740.         {
  741.             double theta=Util.angleDiff(Math.atan2(pointCY(nf[n]-1)-y1,pointCX(nf[n]-1)-x1),btheta);
  742.             if (theta>0) {if (idxFLeft!=0) return null; idxFLeft=nf[n];}
  743.             else {if (idxFRight!=0) return null; idxFRight=nf[n];}
  744.         }
  745.         for (int n=0;n<nt.length;n++) if (nt[n]!=bf)
  746.         {
  747.             double theta=Util.angleDiff(Math.atan2(pointCY(nt[n]-1)-y2,pointCX(nt[n]-1)-x2),btheta);
  748.             if (theta>0) {if (idxTLeft!=0) return null; idxTLeft=nt[n];}
  749.             else {if (idxTRight!=0) return null; idxTRight=nt[n];}
  750.         }
  751.        
  752.         int sumFrom=(idxFLeft>0 ? 1 : 0)+(idxFRight>0 ? 1 : 0),sumTo=(idxTLeft>0 ? 1 : 0)+(idxTRight>0 ? 1 : 0);
  753.        
  754.         if (sumFrom==1 && sumTo==0) return new int[]{idxFLeft>0 ? idxFLeft : idxFRight};
  755.         if (sumFrom==0 && sumTo==1) return new int[]{idxTLeft>0 ? idxTLeft : idxTRight};
  756.         if (sumFrom==1 && sumTo==1)
  757.         {
  758.             // cis? if so, then side is obvious
  759.             if (idxFLeft>0 && idxTLeft>0) return new int[]{idxFLeft,idxTLeft};
  760.             if (idxFRight>0 && idxTRight>0) return new int[]{idxFRight,idxTRight};
  761.            
  762.             // trans? either is fine, so go with congestion
  763.             double[] oxy=orthogonalDelta(x1,y1,x2,y2,bondSep);
  764.             double congestLeft=spatialCongestion(0.5*(x1+x2)+oxy[0],0.5*(y1+y2)+oxy[1]);
  765.             double congestRight=spatialCongestion(0.5*(x1+x2)-oxy[0],0.5*(y1+y2)-oxy[1]);
  766.             if (congestLeft<congestRight) return new int[]{idxFLeft>0 ? idxFLeft : idxTLeft};
  767.             else return new int[]{idxFRight>0 ? idxFRight : idxTRight};
  768.         }
  769.         if (sumFrom==2 && sumTo==1)
  770.         {
  771.             // side with the majority
  772.             if (idxTLeft==0) return new int[]{idxFRight,idxTRight};
  773.             else return new int[]{idxFLeft,idxTLeft};
  774.         }
  775.         if (sumFrom==1 && sumTo==2)
  776.         {
  777.             // side with the majority
  778.             if (idxFLeft==0) return new int[]{idxFRight,idxTRight};
  779.             else return new int[]{idxFLeft,idxTLeft};
  780.         }
  781.  
  782.         return null;
  783.     }
  784.    
  785.     // for the lines L1-->L2 and L3-->L4, calculate and return the intersection; (note lines, not line segments)
  786.     private double[] lineIntersection(double x1,double y1,double x2,double y2,double x3,double y3,double x4,double y4)
  787.     {
  788.         double u=((x4-x3)*(y1-y3) - (y4-y3)*(x1-x3))
  789.                 /((y4-y3)*(x2-x1) - (x4-x3)*(y2-y1));
  790.         return new double[]{x1+u*(x2-x1),y1+u*(y2-y1)};
  791.     }
  792.    
  793.     // for a specific location, returns a measure of how "congested" it is; lower values mean that the point is generally far away
  794.     // from things
  795.     private double spatialCongestion(double x,double y)
  796.     {
  797.         double congest=0;
  798.         for (int n=0;n<numPoints();n++) congest+=1/(Util.sqr(pointCX(n)-x)+Util.sqr(pointCY(n)-y));
  799.         return congest;
  800.     }
  801.  
  802.     // returns true if the indicated box intersects with any of the currently defined atom centres or bond lines
  803.     private boolean boxOverlaps(double x,double y,double w,double h)
  804.     {
  805.         Rectangle2D.Double box=new Rectangle2D.Double(x,y,w,h);
  806.         for (int n=0;n<numPoints();n++)
  807.         {
  808.             APoint a=points.get(n);
  809.             double aw=Math.max(a.rw,1),ah=Math.max(a.rh,1);
  810.             if (box.intersects(a.cx-aw,a.cy-ah,2*aw,2*ah)) return true;
  811.         }
  812.         for (int n=0;n<numLines();n++)
  813.         {
  814.             BLine b=lines.get(n);
  815.             double dx=b.x2-b.x1,dy=b.y2-b.y1;
  816.             if (!box.intersects(Math.min(b.x1,b.x2)-1,Math.min(b.y1,b.y2)-1,Math.abs(dx)+2,Math.abs(dy)+2)) continue;
  817.             double stepsz=0.1*0.25*(w+h)/Math.sqrt(dx*dx+dy*dy);
  818.             for (double v=0.0;v<=1.0;v+=stepsz) if (box.contains(b.x1+v*dx,b.y1+v*dy)) return true;
  819.         }
  820.         return false;
  821.     }
  822.    
  823.     // for a given piece of text, tries to find a position close to that which is given, which does not overlap with anything
  824.     private double[] anchorAnnotation(double px,double py,double rw,double rh)
  825.     {
  826.         if (!boxOverlaps(px,py,rw,rh)) return new double[]{px,py}; // just in case it's blank...
  827.        
  828.         // do radial sweeps: take the best angle at the current extension, or if none, loop around with a bigger sweep
  829.         double stepext=0.25*scale,ext=stepext;
  830.         boolean lastChance=false;
  831.         while (true)
  832.         {
  833.             double bestX=0,bestY=0,bestScore=-1;
  834.             for (double th=0;th<2*Math.PI;th+=Math.PI/36) // 5 degree increments
  835.             {
  836.                 double tx=px+ext*Math.cos(th),ty=py+ext*Math.sin(th);
  837.                 if (!lastChance) if (boxOverlaps(tx-rw,ty-rh,2*rw,2*rh)) continue;
  838.                 double score=spatialCongestion(tx,ty);
  839.                 if (bestScore<0 || score<bestScore) {bestScore=score; bestX=tx; bestY=ty;}
  840.             }
  841.             if (bestScore>=0) return new double[]{bestX,bestY};
  842.            
  843.             // nothing flies, so increase the extent; if gone too far, wind it back in, and pick the best of the bad
  844.             ext+=stepext;
  845.             if (ext>3*scale) {ext=rw+rh; lastChance=true;}
  846.         }
  847.     }
  848. }
  849.  
  850.