com.actelion.research.chem.AbstractDepictor Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of openchemlib Show documentation
Show all versions of openchemlib Show documentation
Open Source Chemistry Library
/*
* Copyright (c) 1997 - 2016
* Actelion Pharmaceuticals Ltd.
* Gewerbestrasse 16
* CH-4123 Allschwil, Switzerland
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of the the copyright holder nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* @author Thomas Sander
*/
package com.actelion.research.chem;
import com.actelion.research.gui.editor.AtomQueryFeatureDialogBuilder;
import com.actelion.research.gui.generic.GenericPoint;
import com.actelion.research.gui.generic.GenericPolygon;
import com.actelion.research.gui.generic.GenericRectangle;
import com.actelion.research.util.ColorHelper;
import java.awt.*;
import java.util.ArrayList;
import java.util.Arrays;
public abstract class AbstractDepictor {
/*
* If displayMode includes cDModeColorizeAtomLabels, then all atom labels are drawn
* in a default color, which may be overridden by explicit atom colors or atom error colors.
* The default color affects the atom label and potentially shown implicit hydrogens,
* while explicit atom colors and atom error colors affect half of the connected bonds
* in addition.
* Color values taken from jmol:
* http://jmol.sourceforge.net/jscolors/#Atoms%20%28%27CPK%27%20colors,%20default%20element%20colors%29
*/
private static final int[] ATOM_LABEL_COLOR = { 0x00000000,
0x00FFFFFF, 0x00D9FFFF, 0x00CC80FF, 0x00C2FF00, 0x00FFB5B5, 0x00909090, 0x003050F8, 0x00FF0D0D,
0x0090E050, 0x00B3E3F5, 0x00AB5CF2, 0x008AFF00, 0x00BFA6A6, 0x00F0C8A0, 0x00FF8000, 0x00FFFF30,
0x001FF01F, 0x0080D1E3, 0x008F40D4, 0x003DFF00, 0x00E6E6E6, 0x00BFC2C7, 0x00A6A6AB, 0x008A99C7,
0x009C7AC7, 0x00E06633, 0x00F090A0, 0x0050D050, 0x00C88033, 0x007D80B0, 0x00C28F8F, 0x00668F8F,
0x00BD80E3, 0x00FFA100, 0x00A62929, 0x005CB8D1, 0x00702EB0, 0x0000FF00, 0x0094FFFF, 0x0094E0E0,
0x0073C2C9, 0x0054B5B5, 0x003B9E9E, 0x00248F8F, 0x000A7D8C, 0x00006985, 0x00C0C0C0, 0x00FFD98F,
0x00A67573, 0x00668080, 0x009E63B5, 0x00D47A00, 0x00940094, 0x00429EB0, 0x0057178F, 0x0000C900,
0x0070D4FF, 0x00FFFFC7, 0x00D9FFC7, 0x00C7FFC7, 0x00A3FFC7, 0x008FFFC7, 0x0061FFC7, 0x0045FFC7,
0x0030FFC7, 0x001FFFC7, 0x0000FF9C, 0x0000E675, 0x0000D452, 0x0000BF38, 0x0000AB24, 0x004DC2FF,
0x004DA6FF, 0x002194D6, 0x00267DAB, 0x00266696, 0x00175487, 0x00D0D0E0, 0x00FFD123, 0x00B8B8D0,
0x00A6544D, 0x00575961, 0x009E4FB5, 0x00AB5C00, 0x00754F45, 0x00428296, 0x00420066, 0x00007D00,
0x0070ABFA, 0x0000BAFF, 0x0000A1FF, 0x00008FFF, 0x000080FF, 0x00006BFF, 0x00545CF2, 0x00785CE3,
0x008A4FE3, 0x00A136D4, 0x00B31FD4, 0x00B31FBA, 0x00B30DA6, 0x00BD0D87, 0x00C70066, 0x00CC0059,
0x00D1004F, 0x00D90045, 0x00E00038, 0x00E6002E, 0x00EB0026, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000,
0x00C8C8C8, 0x00145AFF, 0x0000DCDC, 0x00E60A0A, 0x00E6E600, 0x0000DCDC, 0x00E60A0A, 0x00EBEBEB,
0x008282D2, 0x000F820F, 0x000F820F, 0x00145AFF, 0x00E6E600, 0x003232AA, 0x00DC9682, 0x00FA9600,
0x00FA9600, 0x00B45AB4, 0x003232AA, 0x000F820F
};
private static final int BOND_FG_HILITE_COLOR = 0xFFFF8000;
private static final int BOND_BG_HILITE_COLOR = 0xFF5CA0FF;
private static final int FG_EXCLUDE_GROUP_COLOR = 0xFFA00040;
private static final int BG_EXCLUDE_GROUP_COLOR = 0xFFFFA0FF;
private static final int COLOR_SELECTED = Molecule.cAtomColorRed;
private static final int COLOR_CIP_LETTER = Molecule.cAtomColorDarkRed;
private static final int COLOR_CHIRALITY_TEXT = Molecule.cAtomColorDarkRed;
private static final int COLOR_UNDEFINED = -1;
private static final int COLOR_HILITE_BOND_BG = -2;
private static final int COLOR_HILITE_BOND_FG = -3;
private static final int COLOR_OVERRULED = -4;
private static final int COLOR_RGB = -5;
private static final int COLOR_CUSTOM_FOREGROUND = -6;
private static final int COLOR_EXCLUDE_GROUP_BG = -7;
private static final int COLOR_EXCLUDE_GROUP_FG = -8;
private static final int COLOR_RESTORE_PREVIOUS = -9;
private static final int COLOR_INITIALIZE = -10;
public static final int COLOR_BLUE = 0xFF2060FF;
public static final int COLOR_RED = 0xFFFF0000;
public static final int COLOR_GREEN = 0xFF00FF00;
public static final int COLOR_MAGENTA = 0xFFC000FF;
public static final int COLOR_ORANGE = 0xFFFFA000;
public static final int COLOR_DARK_GREEN = 0xFF008000;
public static final int COLOR_DARK_RED = 0xFFA00000;
public static final int cOptAvBondLen = (int)Molecule.cDefaultAVBL;
public static final int cColorGray = 1; // avoid the Molecule.cAtomFlagsColor range
protected static final int cModeMaxBondLength = 0x0FFFF;
protected static final int cModeInflateToAVBL = 0x30000;
private static final int cModeChiralTextLocation = 0xC0000;
// options for validateView() and updateCoords()
public static final int cModeInflateToMaxAVBL = 0x10000;
public static final int cModeInflateToHighResAVBL = 0x20000; // like cModeInflateToMaxAVBL, but avbl is multiplied by 256 before encoding
public static final int cModeChiralTextBelowMolecule = 0x00000;
public static final int cModeChiralTextAboveMolecule = 0x40000;
public static final int cModeChiralTextOnFrameTop = 0x80000;
public static final int cModeChiralTextOnFrameBottom = 0xC0000;
public static final int cDModeNoTabus = 0x0001; // these are the display mode options
public static final int cDModeAtomNo = 0x0002;
public static final int cDModeBondNo = 0x0004;
public static final int cDModeHiliteAllQueryFeatures = 0x0008; // Hilite also atoms and bonds with obvious query features
public static final int cDModeShowMapping = 0x0010;
public static final int cDModeSuppressChiralText = 0x0020;
public static final int cDModeSuppressCIPParity = 0x0040;
public static final int cDModeSuppressESR = 0x0080;
private static final int cDModeShowSymmetryAny = 0x0300;
public static final int cDModeShowSymmetrySimple = 0x0100;
public static final int cDModeShowSymmetryStereoHeterotopicity = 0x0200;
public static final int cDModeNoImplicitAtomLabelColors = 0x0400;
public static final int cDModeNoStereoProblem = 0x0800;
public static final int cDModeNoColorOnESRAndCIP = 0x1000;
public static final int cDModeNoImplicitHydrogen = 0x2000;
public static final int cDModeDrawBondsInGray = 0x4000;
private static final double cFactorTextSize = 0.6;
private static final double cFactorChiralTextSize = 0.5;
private static final double cFactorBondSpacing = 0.15;
private static final double cFactorBondHiliteRadius = 0.38;
private static final double cFactorExcludeGroupRadius = 0.47;
private static final double cFactorDotDiameter = 0.12;
private static final double cFactorQFDiameter = 0.40;
private static final double cFactorLineWidth = 0.06;
private boolean[] mAtomIsConnected;
private boolean[] mAtomLabelDisplayed;
private double mpBondSpacing,mpDotDiameter,mpLineWidth,mpQFDiameter,mpBondHiliteRadius,
mFactorTextSize,mpExcludeGroupRadius,mChiralTextSize,mAtomLabelAVBL;
private int mpLabelSize,mStandardForegroundColor,mDisplayMode,mCurrentColor,mPreviousColor;
private boolean mIsValidatingView;
private ArrayList mpTabuZone;
private ArrayList mpDot;
private StereoMolecule mMol;
private GenericRectangle mBoundingRect = new GenericRectangle();
private DepictorTransformation mTransformation;
private GenericPoint mChiralTextLocation;
private int[] mAtomColor,mAtomHiliteColor;
private float[] mAtomHiliteRadius;
private String[] mAtomText;
private GenericPoint[] mAlternativeCoords;
private int mOverruleForeground,mOverruleBackground,mBondBGHiliteColor,mBondFGHiliteColor,
mExcludeGroupFGColor,mExcludeGroupBGColor,mCustomForeground,mCustomBackground,
mRGBColor;
protected T mContext;
public AbstractDepictor(StereoMolecule mol) {
this(mol, 0);
}
public AbstractDepictor(StereoMolecule mol, int displayMode) {
mMol = mol;
mDisplayMode = displayMode;
init();
}
public void setDisplayMode(int displayMode) {
mDisplayMode = displayMode;
}
/**
* Defines additional atom text to be displayed in top right
* position of some/all atom label. If the atom is charged, then
* the atom text follows the charge information.
* @param atomText null or String array matching atom indexes (may contain null entries)
*/
public void setAtomText(String[] atomText) {
mAtomText = atomText;
}
/**
* If the foreground color is set (!=0), then molecule is drawn in the foreground
* color except for non-carbon atoms, which are drawn in atomicNo specific
* colors. If a background color is given (!=0), then atom coloring and highlighting
* is optimized to achieve good contrasts.
* @param foreground null (black) or color to be used for molecule drawing
* @param background null (white) or color on which the molecule is drawn
*/
@Deprecated
public void setForegroundColor(Color foreground, Color background) {
setForegroundColor(foreground == null ? 0 : foreground.getRGB(),
background == null ? 0 : background.getRGB());
}
/**
* If the foreground color is set (!=0), then molecule is drawn in the foreground
* color except for non-carbon atoms, which are drawn in atomicNo specific
* colors. If a background color is given (!=0), then atom coloring and highlighting
* is optimized to achieve good contrasts.
* @param foreground 0 or ARGB values of color to be used for molecule drawing
* @param background 0 or ARGB values of color on which the molecule is drawn
*/
public void setForegroundColor(int foreground, int background) {
mStandardForegroundColor = COLOR_CUSTOM_FOREGROUND;
mCustomForeground = foreground;
mCustomBackground = background;
updateBondHiliteColor();
}
/**
* If the overrule color is set, the entire molecule is drawn in the foreground
* color neglecting any atom color information. The background color is used
* to construct a proper bond hilite color, if bond hiliting is used.
* @param foreground null or color to be used for molecule drawing
* @param background may be null
*/
@Deprecated
public void setOverruleColor(Color foreground, Color background) {
setOverruleColor(foreground == null ? 0 : foreground.getRGB(),
background == null ? 0 : background.getRGB());
}
/**
* If the overrule color is set (!=0), then entire molecule is drawn in the foreground
* color neglecting any atom color information. The background color is used
* to construct a proper bond hilite color, if bond hiliting is used.
* @param foreground 0 or ARGB values of color to be used for molecule drawing
* @param background 0 or ARGB values of background color
*/
public void setOverruleColor(int foreground, int background) {
mOverruleForeground = foreground;
mOverruleBackground = background;
updateBondHiliteColor();
}
/**
* If you want the Depictor to draw an atom background with specific colors for every atom,
* then you need to call this method before calling paint().
* @param argb values with a==0 are not considered
* @param radius <= 1.0; if null, then a default of 0.5 of the average bond length is used
*/
public void setAtomHighlightColors(int[] argb, float[] radius) {
mAtomHiliteColor = argb;
mAtomHiliteRadius = radius;
}
public void setTransformation(DepictorTransformation t) {
mTransformation = t;
}
/**
* Per default the size of the atom label font depends on the average bond length,
* which usually is determined before a molecule is drawn.
* This method allows to override the mechanism and to define an artificial
* "average bond length" that is used instead if the real one to serve as basis
* for the scale of atom labels, double bond distance, chiral text, etc...
* @param avbl
*/
public void setAtomLabelAVBL(double avbl) {
mAtomLabelAVBL = avbl / mTransformation.getScaling();
}
/**
* Sets a multiplication factor to the text size of all labels. The default is 1.0.
* @param factor text size factor
*/
public void setFactorTextSize(double factor) {
mFactorTextSize = factor;
}
public DepictorTransformation getTransformation() {
return mTransformation;
}
public void applyTransformation(DepictorTransformation t) {
t.applyTo(mTransformation);
t.applyTo(mBoundingRect);
t.applyTo(mChiralTextLocation);
}
/**
* Scales and translates the depictor's molecule's coordinates such that the molecule
* fits into the supplied rectangle.
* This method considers atom labels when generating the bounding box, which includes
* atom labels of drawn implicit hydrogen atoms.
* @param context
* @param viewRect
* @param mode is typically (cModeInflateToMaxAVBL | maximum_desired_bond_length)
* @return transformation that moved/scaled the original molecule into viewRect
*/
public DepictorTransformation updateCoords(T context, GenericRectangle viewRect, int mode) {
validateView(context, viewRect, mode);
if (mTransformation.isVoidTransformation()) {
return null;
}
else {
DepictorTransformation t = mTransformation;
mTransformation.applyTo(mMol);
mTransformation = new DepictorTransformation();
return t;
}
}
/**
* Scales and translates the depictor's molecule's coordinates such that the molecule
* fits into the supplied rectangle.
* This simple method creates a transformation that places all atom coordinates in
* the viewRect but does not know about nor considers atom labels.
* @param viewRect
* @param mode
* @return transformation that moved/scaled the original molecule into viewRect
*/
public DepictorTransformation simpleUpdateCoords(GenericRectangle viewRect, int mode) {
simpleValidateView(viewRect, mode);
if (mTransformation.isVoidTransformation()) {
return null;
}
else {
DepictorTransformation t = mTransformation;
mTransformation.applyTo(mMol);
mTransformation = new DepictorTransformation();
return t;
}
}
public StereoMolecule getMolecule() {
return mMol;
}
/**
* A depictor maintains a DepictorTransformation object, which defines translation and scaling
* needed to project all of the molecule's coordinates into the viewRect.
* This method updates the depictor's transformation
* such that the molecule is centered in viewRect and reduced in size if it doesn't fit.
* If mode contains cModeInflateToMaxAVBL, then it is scaled to reach the desired mean bond length,
* or to fill viewRect, whatever is reached first.
* @param context
* @param viewRect
* @param mode (<0> or cModeInflateToMaxAVBL) +
* @return incremental transformation being applied to depictor's current transformation
*/
public DepictorTransformation validateView(T context, GenericRectangle viewRect, int mode) {
if (mMol.getAllAtoms() == 0)
return null;
DepictorTransformation t1 = simpleValidateView(viewRect, mode);
mMol.ensureHelperArrays(requiredHelperArrays());
markIsolatedAtoms();
mpDot.clear();
mpTabuZone.clear();
mContext = context;
calculateParameters();
mpSetNormalLabelSize();
mIsValidatingView = true;
for (int i=0; i getAtomX(i)) minx = getAtomX(i);
if (maxx < getAtomX(i)) maxx = getAtomX(i);
if (miny > getAtomY(i)) miny = getAtomY(i);
if (maxy < getAtomY(i)) maxy = getAtomY(i);
}
return new GenericRectangle(minx, miny, maxx-minx, maxy-miny);
}
private void expandBoundsByTabuZones(double avbl) {
for (int i=0; i viewRect.y + viewRect.height - spacing)
mChiralTextLocation.y = viewRect.y + viewRect.height - spacing;
break;
case cModeChiralTextOnFrameTop:
if (viewRect != null) {
mChiralTextLocation.x = viewRect.x + viewRect.width/2.0;
mChiralTextLocation.y = viewRect.y + spacing;
break;
}
case cModeChiralTextAboveMolecule:
mChiralTextLocation.x = mBoundingRect.x + mBoundingRect.width/2.0;
mChiralTextLocation.y = mBoundingRect.y - spacing;
if (viewRect != null && mChiralTextLocation.y < viewRect.y + spacing)
mChiralTextLocation.y = viewRect.y + spacing;
break;
}
}
/**
* @param atom
* @return atom x-coordinate in Depictor's display space
*/
public double getAtomX(int atom) {
return mTransformation.transformX(mMol.getAtomX(atom));
}
/**
* @param atom
* @return atom y-coordinate in Depictor's display space
*/
public double getAtomY(int atom) {
return mTransformation.transformY(mMol.getAtomY(atom));
}
/**
* Requires a call of updateCoords() or validateView() before calling this method.
* @return the bounding rectangle in device coordinates (of the moved/scaled molecule)
*/
public final GenericRectangle getBoundingRect() {
return mBoundingRect;
}
protected void init() {
mFactorTextSize = 1.0;
mTransformation = new DepictorTransformation();
mpTabuZone = new ArrayList<>();
mpDot = new ArrayList<>();
mAtomLabelDisplayed = new boolean[mMol.getAllAtoms()];
mChiralTextLocation = new GenericPoint();
mStandardForegroundColor = Molecule.cAtomColorNone;
mCurrentColor = COLOR_UNDEFINED;
updateBondHiliteColor();
}
private void updateBondHiliteColor() {
int background = (mOverruleBackground != 0) ? mOverruleBackground
: (mCustomBackground != 0) ? mCustomBackground : 0xFFFFFFFF;
mBondBGHiliteColor = ColorHelper.intermediateColor(background, BOND_BG_HILITE_COLOR, 0.3f);
mBondFGHiliteColor = ColorHelper.getContrastColor(BOND_FG_HILITE_COLOR, background);
mExcludeGroupBGColor = BG_EXCLUDE_GROUP_COLOR;
mExcludeGroupFGColor = FG_EXCLUDE_GROUP_COLOR;
}
private void calculateParameters() {
double averageBondLength = mTransformation.getScaling() * (mAtomLabelAVBL != 0 ? mAtomLabelAVBL : mMol.getAverageBondLength());
mpLineWidth = averageBondLength * cFactorLineWidth;
mpBondSpacing = averageBondLength * cFactorBondSpacing;
mpBondHiliteRadius = averageBondLength * cFactorBondHiliteRadius;
mpExcludeGroupRadius = averageBondLength * cFactorExcludeGroupRadius;
mpLabelSize = (int)(averageBondLength * mFactorTextSize * cFactorTextSize + 0.5);
mpDotDiameter = averageBondLength * cFactorDotDiameter;
mpQFDiameter = averageBondLength * cFactorQFDiameter;
mChiralTextSize = averageBondLength * cFactorChiralTextSize + 0.5f;
}
public synchronized void paint(T context) {
if (mMol.getAllAtoms() == 0)
return;
mMol.ensureHelperArrays(requiredHelperArrays());
mContext = context;
calculateParameters();
int[][] esrGroupMemberCount = mMol.getESRGroupMemberCounts();
boolean explicitAtomColors = false;
mAtomColor = new int[mMol.getAllAtoms()];
for (int atom=0; atom= mMol.getAtoms()))
hiliteAtomBackgrounds(mAtomHiliteColor, mAtomHiliteRadius);
hiliteExcludeGroups();
hiliteBondBackgrounds();
indicateQueryFeatures();
addChiralInfo();
mpSetNormalLabelSize();
setLineWidth(mpLineWidth);
setColorCode(mStandardForegroundColor);
markIsolatedAtoms();
mpDot.clear();
mpTabuZone.clear();
if ((mDisplayMode & cDModeNoTabus) != 0) {
// we draw bonds first
mpDrawAllBonds(esrGroupMemberCount);
mpDrawAllDots();
mpDrawBondQueryFeatures();
}
for (int i=0; i>> 24;
if (alpha != 0) {
int rgb = argb[atom];
if (alpha != 255)
rgb = ColorHelper.intermediateColor(background, argb[atom], (float)alpha/255f);
double r = (radius == null) ? 0.5 * avbl : 0.6 * radius[atom] * avbl;
setRGB(rgb);
fillCircle(getAtomX(atom)-r, getAtomY(atom)-r, 2*r);
}
}
}
private void hiliteBondBackgrounds() {
setLineWidth(2*mpBondHiliteRadius);
DepictorLine line = new DepictorLine();
for (int bond=0; bond 1) {
if (mMol.getBondCIPParity(i) == Molecule.cBondCIPParityEorP)
cipStr = (mMol.getBondOrder(i) == 2) ? "E" : mMol.isBondParityPseudo(i) ? "p" : "P";
else
cipStr = (mMol.getBondOrder(i) == 2) ? "Z" : mMol.isBondParityPseudo(i) ? "m" : "M";
}
}
else {
cipStr = "?";
}
if (cipStr != null) {
mpSetSmallLabelSize();
setColorCode(mMol.isBondForegroundHilited(i) ? COLOR_HILITE_BOND_FG :
(mMol.getMoleculeColor() == Molecule.cMoleculeColorNeutral || (mDisplayMode & cDModeNoColorOnESRAndCIP) != 0) ?
mStandardForegroundColor : COLOR_CIP_LETTER);
int atom1 = mMol.getBondAtom(0,i);
int atom2 = mMol.getBondAtom(1,i);
double x = (getAtomX(atom1) + getAtomX(atom2)) / 2;
double y = (getAtomY(atom1) + getAtomY(atom2)) / 2;
double dx = (getAtomX(atom1) - getAtomX(atom2)) / 3;
double dy = (getAtomY(atom1) - getAtomY(atom2)) / 3;
mpDrawString(x+dy,y-dx, cipStr,true);
setColorCode(mStandardForegroundColor);
mpSetNormalLabelSize();
}
}
}
}
if ((mDisplayMode & cDModeBondNo) != 0) {
mpSetSmallLabelSize();
setColorCode(Molecule.cAtomColorDarkGreen);
for (int i=0; i 0) {
bLine.x1 = theLine.x1 + piBondOffset.x;
bLine.y1 = theLine.y1 + piBondOffset.y;
bLine.x2 = theLine.x2 + piBondOffset.x;
bLine.y2 = theLine.y2 + piBondOffset.y;
if (mpCalcNextBondOffset(atom1, atom2, 1, nextBondOffset)
|| (mMol.getConnAtoms(atom1) > 1)) {
bLine.x1 += nextBondOffset.x + piBondOffset.y;
bLine.y1 += nextBondOffset.y - piBondOffset.x;
}
if (mpCalcNextBondOffset(atom2, atom1, -1, nextBondOffset)
|| (mMol.getConnAtoms(atom2) > 1)) {
bLine.x2 += nextBondOffset.x - piBondOffset.y;
bLine.y2 += nextBondOffset.y + piBondOffset.x;
}
}
else {
bLine.x1 = theLine.x1 - piBondOffset.x;
bLine.y1 = theLine.y1 - piBondOffset.y;
bLine.x2 = theLine.x2 - piBondOffset.x;
bLine.y2 = theLine.y2 - piBondOffset.y;
if (mpCalcNextBondOffset(atom1, atom2, -1, nextBondOffset)
|| (mMol.getConnAtoms(atom1) > 1)) {
bLine.x1 += nextBondOffset.x + piBondOffset.y;
bLine.y1 += nextBondOffset.y - piBondOffset.x;
}
if (mpCalcNextBondOffset(atom2, atom1, 1, nextBondOffset)
|| (mMol.getConnAtoms(atom2) > 1)) {
bLine.x2 += nextBondOffset.x - piBondOffset.y;
bLine.y2 += nextBondOffset.y + piBondOffset.x;
}
}
if (mMol.getBondType(bnd) == Molecule.cBondTypeCross)
mpMakeCrossBond(aLine,bLine);
mpHandleLine(aLine, atom1, atom2);
if (bondOrder == 2)
mpHandleLine(bLine, atom1, atom2);
else
mpHandleDashedLine(bLine, atom1, atom2);
}
break;
case 3:
if (mpProperLine(theLine)) {
drawLine(theLine, atom1, atom2);
mpCalcPiBondOffset(theLine.x2 - theLine.x1,
theLine.y2 - theLine.y1, piBondOffset);
drawOffsetLine(theLine, atom1, atom2, piBondOffset.x, piBondOffset.y, aLine);
drawOffsetLine(theLine, atom1, atom2, -piBondOffset.x, -piBondOffset.y, aLine);
}
break;
case 4:
if (mpProperLine(theLine)) {
mpCalcPiBondOffset(theLine.x2 - theLine.x1,
theLine.y2 - theLine.y1, piBondOffset);
drawOffsetLine(theLine, atom1, atom2, 1.5*piBondOffset.x, 1.5*piBondOffset.y, aLine);
drawOffsetLine(theLine, atom1, atom2, 0.5*piBondOffset.x, 0.5*piBondOffset.y, aLine);
drawOffsetLine(theLine, atom1, atom2, -0.5*piBondOffset.x, -0.5*piBondOffset.y, aLine);
drawOffsetLine(theLine, atom1, atom2, -1.5*piBondOffset.x, -1.5*piBondOffset.y, aLine);
}
break;
case 5:
if (mpProperLine(theLine)) {
drawLine(theLine, atom1, atom2);
mpCalcPiBondOffset(theLine.x2 - theLine.x1,
theLine.y2 - theLine.y1, piBondOffset);
drawOffsetLine(theLine, atom1, atom2, 2*piBondOffset.x, 2*piBondOffset.y, aLine);
drawOffsetLine(theLine, atom1, atom2, piBondOffset.x, piBondOffset.y, aLine);
drawOffsetLine(theLine, atom1, atom2, -piBondOffset.x, -piBondOffset.y, aLine);
drawOffsetLine(theLine, atom1, atom2, -2*piBondOffset.x, -2*piBondOffset.y, aLine);
}
break;
}
if (mCurrentColor == COLOR_EXCLUDE_GROUP_FG)
setColorCode(COLOR_RESTORE_PREVIOUS);
}
private void drawOffsetLine(DepictorLine theLine, int atom1, int atom2, double dx, double dy, DepictorLine aLine) {
aLine.x1 = theLine.x1 + dx;
aLine.y1 = theLine.y1 + dy;
aLine.x2 = theLine.x2 + dx;
aLine.y2 = theLine.y2 + dy;
drawLine(aLine, atom1, atom2);
}
private void mpDBFromNonLabelToLabel(DepictorLine theLine, int bnd, boolean inverted) {
DepictorLine aLine = new DepictorLine();
DepictorLine bLine = new DepictorLine();
GenericPoint piBondOffset = new GenericPoint();
GenericPoint nextBondOffset = new GenericPoint();
int atm1 = mMol.getBondAtom(0,bnd);
int atm2 = mMol.getBondAtom(1,bnd);
if (inverted) {
double td = theLine.x1;
theLine.x1 = theLine.x2;
theLine.x2 = td;
td = theLine.y1;
theLine.y1 = theLine.y2;
theLine.y2 = td;
int ti = atm1;
atm1 = atm2;
atm2 = ti;
}
if (!mpProperLine(theLine))
return;
if (mMol.isRingBond(bnd)) { // don't draw a centered double bond when
// bond is in a ring
aLine.x1 = theLine.x1;
aLine.y1 = theLine.y1;
aLine.x2 = theLine.x2;
aLine.y2 = theLine.y2;
int side = (inverted) ? -mMol.getPreferredDoubleBondSide(bnd) : mMol.getPreferredDoubleBondSide(bnd);
if (side == 0) side = 1;
mpCalcPiBondOffset(theLine.x2 - theLine.x1,
theLine.y2 - theLine.y1,piBondOffset);
if (side > 0) {
bLine.x1 = theLine.x1 + piBondOffset.x;
bLine.y1 = theLine.y1 + piBondOffset.y;
bLine.x2 = theLine.x2 + piBondOffset.x;
bLine.y2 = theLine.y2 + piBondOffset.y;
if (mpCalcNextBondOffset(atm1,atm2,1,nextBondOffset)
|| (mMol.getConnAtoms(atm1) > 1)) {
bLine.x1 += nextBondOffset.x + piBondOffset.y;
bLine.y1 += nextBondOffset.y - piBondOffset.x;
}
}
else {
bLine.x1 = theLine.x1 - piBondOffset.x;
bLine.y1 = theLine.y1 - piBondOffset.y;
bLine.x2 = theLine.x2 - piBondOffset.x;
bLine.y2 = theLine.y2 - piBondOffset.y;
if (mpCalcNextBondOffset(atm1,atm2,-1,nextBondOffset)
|| (mMol.getConnAtoms(atm1) > 1)) {
bLine.x1 += nextBondOffset.x + piBondOffset.y;
bLine.y1 += nextBondOffset.y - piBondOffset.x;
}
}
if (mMol.getBondType(bnd) == Molecule.cBondTypeCross)
mpMakeCrossBond(aLine,bLine);
mpHandleLine(aLine,atm1,atm2);// the central line
if (mMol.getBondType(bnd) == Molecule.cBondTypeDelocalized)
mpHandleDashedLine(bLine,atm1,atm2);
else
mpHandleLine(bLine,atm1,atm2);
}
else {
mpCalcPiBondOffset(theLine.x2 - theLine.x1,
theLine.y2 - theLine.y1,piBondOffset);
double xdiff = piBondOffset.x / 2;
double ydiff = piBondOffset.y / 2;
boolean aLineIsInnerLine = false;
// boolean bLineIsInnerLine = false;
aLine.x1 = theLine.x1 + xdiff;
aLine.y1 = theLine.y1 + ydiff;
aLine.x2 = theLine.x2 + xdiff;
aLine.y2 = theLine.y2 + ydiff;
if (mMol.getConnAtoms(atm1) > 1) {
if (!mpCalcNextBondOffset(atm1,atm2,1,nextBondOffset)) {
mAlternativeCoords[atm1] = new GenericPoint(aLine.x1, aLine.y1);
// bLineIsInnerLine = true;
}
else {
aLine.x1 += nextBondOffset.x;
aLine.y1 += nextBondOffset.y;
if (mMol.getConnAtoms(atm1) == 2) {
if (nextBondOffset.x != 0 || nextBondOffset.y != 0) {
aLine.x1 += piBondOffset.y;
aLine.y1 -= piBondOffset.x;
}
}
}
}
bLine.x1 = theLine.x1 - xdiff;
bLine.y1 = theLine.y1 - ydiff;
bLine.x2 = theLine.x2 - xdiff;
bLine.y2 = theLine.y2 - ydiff;
if (mMol.getConnAtoms(atm1) > 1) {
if (!mpCalcNextBondOffset(atm1,atm2,0,nextBondOffset)) {
mAlternativeCoords[atm1] = new GenericPoint(bLine.x1, bLine.y1);
aLineIsInnerLine = true;
}
else {
bLine.x1 += nextBondOffset.x;
bLine.y1 += nextBondOffset.y;
if (mMol.getConnAtoms(atm1) == 2) {
if (nextBondOffset.x != 0 || nextBondOffset.y != 0) {
bLine.x1 += piBondOffset.y;
bLine.y1 -= piBondOffset.x;
}
}
}
}
if (mMol.getBondType(bnd) == Molecule.cBondTypeCross)
mpMakeCrossBond(aLine,bLine);
if (mMol.getBondType(bnd) == Molecule.cBondTypeDelocalized) {
if (aLineIsInnerLine) {
drawDashedLine(aLine,atm1,atm2);
drawLine(bLine,atm1,atm2);
}
else {
drawLine(aLine,atm1,atm2);
drawDashedLine(bLine,atm1,atm2);
}
}
else {
drawLine(aLine,atm1,atm2);
drawLine(bLine,atm1,atm2);
}
}
}
private void mpMakeCrossBond(DepictorLine aLine, DepictorLine bLine) {
double temp;
temp = aLine.x2;
aLine.x2 = bLine.x2;
bLine.x2 = temp;
temp = aLine.y2;
aLine.y2 = bLine.y2;
bLine.y2 = temp;
}
private void mpCalcPiBondOffset(double dx, double dy, GenericPoint piBondOffset) {
if (dx == 0) {
if (dy < 0)
piBondOffset.x = mpBondSpacing;
else
piBondOffset.x = - mpBondSpacing;
piBondOffset.y = 0;
return;
}
double alpha = Math.atan(dy / dx);
if (dx < 0)
alpha += Math.PI;
piBondOffset.x = - (mpBondSpacing * Math.sin(alpha));
piBondOffset.y = (mpBondSpacing * Math.cos(alpha));
}
private boolean mpProperLine(DepictorLine theLine) {
// cuts line ends according to needs of involved atoms and returns
// 'false' if line lies entirely in tabuZones,otherwise it returns 'true'
boolean endsExchanged,retval;
if (theLine.x1 == theLine.x2 && theLine.y1 == theLine.y2) {
for (GenericRectangle tabuZone:mpTabuZone)
if (tabuZone.contains(theLine.x1, theLine.y1))
return false;
return true;
}
GenericRectangle theFrame = mpGetFrame(theLine);
endsExchanged = false;
if (theLine.x1 > theLine.x2) { // first point is the one with smaller x
mpExchangeLineEnds(theLine);
endsExchanged = true;
}
for (int i=0; i theFrame.x + theFrame.width
|| tabuZone.y > theFrame.y + theFrame.height
|| theFrame.x > tabuZone.x + tabuZone.width
|| theFrame.y > tabuZone.y + tabuZone.height)
continue;
if (mpInTabuZone(theLine.x1,theLine.y1,i)) {
if (mpInTabuZone(theLine.x2,theLine.y2,i)) {
if (endsExchanged)
mpExchangeLineEnds(theLine);
return false; // entire Line lies within tabuZone boundaries
}
mpShortenLine(theLine,0,i);
retval = mpProperLine(theLine);
if (endsExchanged)
mpExchangeLineEnds(theLine);
return retval;
}
if (mpInTabuZone(theLine.x2,theLine.y2,i)) {
mpShortenLine(theLine,1,i);
retval = mpProperLine(theLine);
if (endsExchanged)
mpExchangeLineEnds(theLine);
return retval;
}
}
if (endsExchanged)
mpExchangeLineEnds(theLine);
return true;
}
private boolean mpCalcNextBondOffset(int atm1,int atm2,int side,GenericPoint nextBondOffset) {
final double RO_LIMIT = 2.617993878; // right outer angle limit = 150 degrees
final double LO_LIMIT = 3.665191429; // left outer angle limit = 210 degrees
final double RI_LIMIT = 0.523598776; // right inner angle limit = 30 degrees
final double LI_LIMIT = 5.759586531; // left inner angle limit = 330 degrees
boolean retval;
int i,remoteAtm,bnd;
double bondAngle,theBondAngle,testAngle;
double angleDiff,currentAngleDiff,distance;
retval = false;
nextBondOffset.x = 0; // default offset if no bond within angle limit
nextBondOffset.y = 0;
if (side > 0)
angleDiff = RO_LIMIT;
else
angleDiff = LO_LIMIT;
theBondAngle = mMol.getBondAngle(atm1,atm2);
for (i=0; i 0) {
if (currentAngleDiff < Math.PI)
retval = true;
// a bond is leading away from double bond's right side
if (currentAngleDiff > RO_LIMIT)
currentAngleDiff = RO_LIMIT;
if (currentAngleDiff < RI_LIMIT)
currentAngleDiff = RI_LIMIT;
if (currentAngleDiff <= angleDiff) {
angleDiff = currentAngleDiff;
distance = mpBondSpacing * Math.tan(angleDiff - Math.PI/2) / 2;
nextBondOffset.x = - (distance * Math.sin(bondAngle));
nextBondOffset.y = - (distance * Math.cos(bondAngle));
}
}
else {
if (currentAngleDiff >= Math.PI)
retval = true;
// a bond is leading away from double bond's left side
if (currentAngleDiff < LO_LIMIT)
currentAngleDiff = LO_LIMIT;
if (currentAngleDiff > LI_LIMIT)
currentAngleDiff = LI_LIMIT;
if (currentAngleDiff >= angleDiff) {
angleDiff = currentAngleDiff;
distance = mpBondSpacing * Math.tan(4.712388981 - angleDiff) / 2;
nextBondOffset.x = - (distance * Math.sin(bondAngle));
nextBondOffset.y = - (distance * Math.cos(bondAngle));
}
}
}
return retval;
}
private void mpExchangeLineEnds(DepictorLine theLine) {
double temp;
temp = theLine.x1;
theLine.x1 = theLine.x2;
theLine.x2 = temp;
temp = theLine.y1;
theLine.y1 = theLine.y2;
theLine.y2 = temp;
}
private void mpHandleLine(DepictorLine theLine,int atm1,int atm2) {
if (mpProperLine(theLine))
drawLine(theLine,atm1,atm2);
}
private void mpHandleDashedLine(DepictorLine theLine,int atm1,int atm2) {
if (mpProperLine(theLine))
drawDashedLine(theLine,atm1,atm2);
}
private void mpHandleShortDashedLine(DepictorLine theLine,int atm1,int atm2) {
if (mpProperLine(theLine))
drawShortDashedLine(theLine, atm1, atm2);
}
private void mpHandleDottedLine(DepictorLine theLine,int atm1,int atm2) {
if (mpProperLine(theLine))
drawDottedLine(theLine);
}
private void mpHandleWedge(DepictorLine origWedge,int atm1,int atm2) {
DepictorLine theWedge = new DepictorLine();
if (origWedge.x1 == origWedge.x2
&& origWedge.y1 == origWedge.y2)
return;
theWedge.x1 = origWedge.x1; // use copy of data for recursive processing
theWedge.y1 = origWedge.y1;
theWedge.x2 = origWedge.x2;
theWedge.y2 = origWedge.y2;
GenericRectangle theFrame = mpGetFrame(theWedge);
for (int i=0; i theFrame.x + theFrame.width
|| tabuZone.y > theFrame.y + theFrame.height
|| theFrame.x > tabuZone.x + tabuZone.width
|| theFrame.y > tabuZone.y + tabuZone.height)
continue;
if (mpInTabuZone(theWedge.x1,theWedge.y1,i)) {
if (mpInTabuZone(theWedge.x2,theWedge.y2,i))
return; // entire Wedge lies within tabuZone boundaries
mpShortenLine(theWedge,0,i);
mpHandleWedge(theWedge,atm1,atm2);
return;
}
if (mpInTabuZone(theWedge.x2,theWedge.y2,i)) {
mpShortenLine(theWedge,1,i);
mpHandleWedge(theWedge,atm1,atm2);
return;
}
}
drawWedge(theWedge,atm1,atm2);
}
private GenericRectangle mpGetFrame(DepictorLine theLine) {
GenericRectangle theFrame = new GenericRectangle();
if (theLine.x1 <= theLine.x2) {
theFrame.x = theLine.x1;
theFrame.width = theLine.x2 - theLine.x1;
}
else {
theFrame.x = theLine.x2;
theFrame.width = theLine.x1 - theLine.x2;
}
if (theLine.y1 <= theLine.y2) {
theFrame.y = theLine.y1;
theFrame.height = theLine.y2 - theLine.y1;
}
else {
theFrame.y = theLine.y2;
theFrame.height = theLine.y1 - theLine.y2;
}
return theFrame;
}
private boolean mpInTabuZone(double x, double y, int tabuZoneNo) {
if ((mDisplayMode & cDModeNoTabus) != 0)
return false;
GenericRectangle tabuZone = mpTabuZone.get(tabuZoneNo);
// cannot use tabuZone.contains() because points on edge would be considered to be within the react
return (x > tabuZone.x
&& x < tabuZone.x + tabuZone.width
&& y > tabuZone.y
&& y < tabuZone.y + tabuZone.height);
}
private void mpShortenLine(DepictorLine theLine,int pointNo,int tabuZoneNo) {
double x1,y1,x2,y2,dx,dy,tabuX,tabuY,sx,sy;
if (pointNo == 0) {
x1 = theLine.x1;
y1 = theLine.y1;
x2 = theLine.x2;
y2 = theLine.y2;
}
else {
x1 = theLine.x2;
y1 = theLine.y2;
x2 = theLine.x1;
y2 = theLine.y1;
}
GenericRectangle tabuZone = mpTabuZone.get(tabuZoneNo);
tabuX = (x2 > x1) ? tabuZone.x+tabuZone.width : tabuZone.x;
tabuY = (y2 > y1) ? tabuZone.y+tabuZone.height : tabuZone.y;
dx = x2 - x1;
dy = y2 - y1;
if (Math.abs(dx) > Math.abs(dy)) {
if (y1 == y2) {
sx = tabuX;
sy = y1;
}
else {
sx = x1 + dx*(tabuY-y1)/dy;
if ((x2>x1)==(tabuX>sx)) {
sy = tabuY;
}
else {
sx = tabuX;
sy = y1 + dy*(tabuX-x1)/dx;
}
}
}
else {
if (x1 == x2) {
sx = x1;
sy = tabuY;
}
else {
sy = y1 + dy*(tabuX-x1)/dx;
if ((y2>y1)==(tabuY>sy)) {
sx = tabuX;
}
else {
sx = x1 + dx*(tabuY-y1)/dy;
sy = tabuY;
}
}
}
if (pointNo == 0) {
theLine.x1 = sx;
theLine.y1 = sy;
}
else {
theLine.x2 = sx;
theLine.y2 = sy;
}
}
private void mpDrawAtom(int atom, int[][] esrGroupMemberCount) {
double chax,chay,xdiff,ydiff,x,y;
if (!mIsValidatingView)
onDrawAtom(atom,mMol.getAtomLabel(atom), getAtomX(atom), getAtomY(atom));
String propStr = null;
if (mMol.getAtomCharge(atom) != 0) {
String valStr = (Math.abs(mMol.getAtomCharge(atom)) == 1) ? ""
: String.valueOf(Math.abs(mMol.getAtomCharge(atom)));
propStr = (mMol.getAtomCharge(atom) < 0) ? valStr + "-" : valStr + "+";
}
if (mAtomText != null && (atom < mAtomText.length) && mAtomText[atom] != null && mAtomText[atom].length() > 0)
propStr = append(propStr, mAtomText[atom]);
String isoStr = null;
long queryFeatures = mMol.getAtomQueryFeatures(atom);
if (queryFeatures != 0) {
if ((queryFeatures & Molecule.cAtomQFIsStereo) != 0)
isoStr = append(isoStr, "*");
if ((queryFeatures & Molecule.cAtomQFIsNotStereo) != 0)
isoStr = append(isoStr, "!*");
if ((queryFeatures & Molecule.cAtomQFHeteroAromatic) != 0)
isoStr = append(isoStr, "ha");
else if ((queryFeatures & Molecule.cAtomQFAromatic) != 0)
isoStr = append(isoStr, "a");
else if ((queryFeatures & Molecule.cAtomQFNotAromatic) != 0)
isoStr = append(isoStr, "!a");
if ((queryFeatures & Molecule.cAtomQFMoreNeighbours) != 0)
isoStr = append(isoStr, "s");
if ((queryFeatures & Molecule.cAtomQFHydrogen) != 0) {
long hydrogens = (queryFeatures & Molecule.cAtomQFHydrogen);
if (hydrogens == Molecule.cAtomQFNot1Hydrogen+Molecule.cAtomQFNot2Hydrogen+Molecule.cAtomQFNot3Hydrogen)
isoStr = append(isoStr, "h0");
else if (hydrogens == Molecule.cAtomQFNot0Hydrogen+Molecule.cAtomQFNot2Hydrogen+Molecule.cAtomQFNot3Hydrogen)
isoStr = append(isoStr, "h1");
else if (hydrogens == Molecule.cAtomQFNot0Hydrogen+Molecule.cAtomQFNot1Hydrogen+Molecule.cAtomQFNot3Hydrogen)
isoStr = append(isoStr, "h2");
else if (hydrogens == Molecule.cAtomQFNot0Hydrogen)
isoStr = append(isoStr, "h>0");
else if (hydrogens == Molecule.cAtomQFNot0Hydrogen+Molecule.cAtomQFNot1Hydrogen)
isoStr = append(isoStr, "h>1");
else if (hydrogens == Molecule.cAtomQFNot0Hydrogen+Molecule.cAtomQFNot1Hydrogen+Molecule.cAtomQFNot2Hydrogen)
isoStr = append(isoStr, "h>2");
else if (hydrogens == Molecule.cAtomQFNot3Hydrogen)
isoStr = append(isoStr, "h<3");
else if (hydrogens == Molecule.cAtomQFNot2Hydrogen+Molecule.cAtomQFNot3Hydrogen)
isoStr = append(isoStr, "h<2");
else if (hydrogens == Molecule.cAtomQFNot0Hydrogen+Molecule.cAtomQFNot3Hydrogen)
isoStr = append(isoStr, "h1-2");
}
if ((queryFeatures & Molecule.cAtomQFCharge) != 0) {
long charge = (queryFeatures & Molecule.cAtomQFCharge);
if (charge == Molecule.cAtomQFNotChargePos+Molecule.cAtomQFNotChargeNeg)
isoStr = append(isoStr, "c0");
else if (charge == Molecule.cAtomQFNotCharge0+Molecule.cAtomQFNotChargeNeg)
isoStr = append(isoStr, "c+");
else if (charge == Molecule.cAtomQFNotCharge0+Molecule.cAtomQFNotChargePos)
isoStr = append(isoStr, "c-");
}
if ((queryFeatures & Molecule.cAtomQFPiElectrons) != 0) {
long piElectrons = (queryFeatures & Molecule.cAtomQFPiElectrons);
if (piElectrons == Molecule.cAtomQFNot1PiElectron+Molecule.cAtomQFNot2PiElectrons)
isoStr = append(isoStr, "pi0");
else if (piElectrons == Molecule.cAtomQFNot0PiElectrons+Molecule.cAtomQFNot2PiElectrons)
isoStr = append(isoStr, "pi1");
else if (piElectrons == Molecule.cAtomQFNot0PiElectrons+Molecule.cAtomQFNot1PiElectron)
isoStr = append(isoStr, "pi2");
else if (piElectrons == Molecule.cAtomQFNot0PiElectrons)
isoStr = append(isoStr, "pi>0");
}
if ((queryFeatures & Molecule.cAtomQFNeighbours) != 0) {
long neighbours = (queryFeatures & Molecule.cAtomQFNeighbours);
if (neighbours == (Molecule.cAtomQFNeighbours & ~Molecule.cAtomQFNot1Neighbour))
isoStr = append(isoStr, "n1");
else if (neighbours == (Molecule.cAtomQFNeighbours & ~Molecule.cAtomQFNot2Neighbours))
isoStr = append(isoStr, "n2");
else if (neighbours == (Molecule.cAtomQFNeighbours & ~Molecule.cAtomQFNot3Neighbours))
isoStr = append(isoStr, "n3");
else if (neighbours == Molecule.cAtomQFNot3Neighbours+Molecule.cAtomQFNot4Neighbours)
isoStr = append(isoStr, "n<3");
else if (neighbours == Molecule.cAtomQFNot4Neighbours)
isoStr = append(isoStr, "n<4");
else if (neighbours == Molecule.cAtomQFNot0Neighbours+Molecule.cAtomQFNot1Neighbour)
isoStr = append(isoStr, "n>1");
else if (neighbours == Molecule.cAtomQFNot0Neighbours+Molecule.cAtomQFNot1Neighbour+Molecule.cAtomQFNot2Neighbours)
isoStr = append(isoStr, "n>2");
else if (neighbours == (Molecule.cAtomQFNeighbours & ~Molecule.cAtomQFNot4Neighbours))
isoStr = append(isoStr, "n>3");
else if (neighbours == (Molecule.cAtomQFNot0Neighbours | Molecule.cAtomQFNot3Neighbours | Molecule.cAtomQFNot4Neighbours))
isoStr = append(isoStr, "n1-2");
else if (neighbours == (Molecule.cAtomQFNot0Neighbours | Molecule.cAtomQFNot4Neighbours))
isoStr = append(isoStr, "n1-3");
else if (neighbours == (Molecule.cAtomQFNot0Neighbours | Molecule.cAtomQFNot1Neighbour | Molecule.cAtomQFNot4Neighbours))
isoStr = append(isoStr, "n2-3");
}
if ((queryFeatures & Molecule.cAtomQFENeighbours) != 0) {
long eNegNeighbours = (queryFeatures & Molecule.cAtomQFENeighbours);
if (eNegNeighbours == (Molecule.cAtomQFENeighbours & ~Molecule.cAtomQFNot0ENeighbours))
isoStr = append(isoStr, "e0");
else if (eNegNeighbours == (Molecule.cAtomQFENeighbours & ~Molecule.cAtomQFNot1ENeighbour))
isoStr = append(isoStr, "e1");
else if (eNegNeighbours == (Molecule.cAtomQFENeighbours & ~Molecule.cAtomQFNot2ENeighbours))
isoStr = append(isoStr, "e2");
else if (eNegNeighbours == (Molecule.cAtomQFENeighbours & ~Molecule.cAtomQFNot3ENeighbours))
isoStr = append(isoStr, "e3");
else if (eNegNeighbours == (Molecule.cAtomQFNot2ENeighbours | Molecule.cAtomQFNot3ENeighbours | Molecule.cAtomQFNot4ENeighbours))
isoStr = append(isoStr, "e<2");
else if (eNegNeighbours == (Molecule.cAtomQFNot3ENeighbours | Molecule.cAtomQFNot4ENeighbours))
isoStr = append(isoStr, "e<3");
else if (eNegNeighbours == Molecule.cAtomQFNot4ENeighbours)
isoStr = append(isoStr, "e<4");
else if (eNegNeighbours == Molecule.cAtomQFNot0ENeighbours)
isoStr = append(isoStr, "e>0");
else if (eNegNeighbours == (Molecule.cAtomQFNot0ENeighbours | Molecule.cAtomQFNot1ENeighbour))
isoStr = append(isoStr, "e>1");
else if (eNegNeighbours == (Molecule.cAtomQFNot0ENeighbours | Molecule.cAtomQFNot1ENeighbour | Molecule.cAtomQFNot2ENeighbours))
isoStr = append(isoStr, "e>2");
else if (eNegNeighbours == (Molecule.cAtomQFENeighbours & ~Molecule.cAtomQFNot4ENeighbours))
isoStr = append(isoStr, "e>3");
else if (eNegNeighbours == (Molecule.cAtomQFNot0ENeighbours | Molecule.cAtomQFNot3ENeighbours | Molecule.cAtomQFNot4ENeighbours))
isoStr = append(isoStr, "e1-2");
else if (eNegNeighbours == (Molecule.cAtomQFNot0ENeighbours | Molecule.cAtomQFNot4ENeighbours))
isoStr = append(isoStr, "e1-3");
else if (eNegNeighbours == (Molecule.cAtomQFNot0ENeighbours | Molecule.cAtomQFNot1ENeighbour | Molecule.cAtomQFNot4ENeighbours))
isoStr = append(isoStr, "e2-3");
}
if ((queryFeatures & Molecule.cAtomQFRingState) != 0) {
long ringBonds = (queryFeatures & Molecule.cAtomQFRingState);
if (ringBonds == Molecule.cAtomQFNot2RingBonds+Molecule.cAtomQFNot3RingBonds+Molecule.cAtomQFNot4RingBonds)
isoStr = append(isoStr, "!r");
else if (ringBonds == Molecule.cAtomQFNotChain)
isoStr = append(isoStr, "r");
else if (ringBonds == Molecule.cAtomQFNot3RingBonds+Molecule.cAtomQFNot4RingBonds)
isoStr = append(isoStr, "rb<3");
else if (ringBonds == Molecule.cAtomQFNotChain+Molecule.cAtomQFNot3RingBonds+Molecule.cAtomQFNot4RingBonds)
isoStr = append(isoStr, "rb2");
else if (ringBonds == Molecule.cAtomQFNotChain+Molecule.cAtomQFNot2RingBonds+Molecule.cAtomQFNot4RingBonds)
isoStr = append(isoStr, "rb3");
else if (ringBonds == Molecule.cAtomQFNotChain+Molecule.cAtomQFNot2RingBonds+Molecule.cAtomQFNot3RingBonds)
isoStr = append(isoStr, "rb4");
}
if ((queryFeatures & Molecule.cAtomQFSmallRingSize) != 0) {
isoStr = append(isoStr, "r"+((queryFeatures & Molecule.cAtomQFSmallRingSize)>>Molecule.cAtomQFSmallRingSizeShift));
}
if ((queryFeatures & Molecule.cAtomQFNewRingSize) != 0) {
isoStr = append(isoStr, createRingSizeText(queryFeatures));
}
if ((queryFeatures & Molecule.cAtomQFFlatNitrogen) != 0) {
isoStr = append(isoStr, "f");
}
}
if (mMol.getAtomMass(atom) != 0) {
isoStr = append(isoStr, String.valueOf(mMol.getAtomMass(atom)));
}
int unpairedElectrons = 0;
if (mMol.getAtomRadical(atom) != 0) {
switch (mMol.getAtomRadical(atom)) {
case Molecule.cAtomRadicalStateS:
propStr = append(propStr, "|");
break;
case Molecule.cAtomRadicalStateD:
unpairedElectrons = 1;
break;
case Molecule.cAtomRadicalStateT:
unpairedElectrons = 2;
break;
}
}
String cipStr = null;
if ((mDisplayMode & cDModeSuppressCIPParity) == 0) {
if (mMol.isAtomConfigurationUnknown(atom))
cipStr = "?";
else if (mMol.getAtomCIPParity(atom) != 0) {
if (mMol.getAtomESRType(atom) == Molecule.cESRTypeAbs
|| esrGroupMemberCount == null
|| esrGroupMemberCount[mMol.getAtomESRType(atom)][mMol.getAtomESRGroup(atom)] > 1) {
if (mMol.getConnAtoms(atom) == 2) {
switch (mMol.getAtomCIPParity(atom)) {
case Molecule.cAtomCIPParitySorP:
cipStr = mMol.isAtomParityPseudo(atom) ? "p" : "P";
break;
case Molecule.cAtomCIPParityRorM:
cipStr = mMol.isAtomParityPseudo(atom) ? "m" : "M";
break;
default:
cipStr = "*";
break;
}
}
else {
switch (mMol.getAtomCIPParity(atom)) {
case Molecule.cAtomCIPParityRorM:
cipStr = mMol.isAtomParityPseudo(atom) ? "r" : "R";
break;
case Molecule.cAtomCIPParitySorP:
cipStr = mMol.isAtomParityPseudo(atom) ? "s" : "S";
break;
default:
cipStr = "*";
break;
}
}
}
}
}
if ((mDisplayMode & cDModeShowSymmetryAny) != 0)
cipStr = append(cipStr, String.valueOf(mMol.getSymmetryRank(atom)));
String mapStr = null;
if ((mDisplayMode & cDModeShowMapping) != 0
&& mMol.getAtomMapNo(atom) != 0)
mapStr = "" + mMol.getAtomMapNo(atom);
String esrStr = null;
if (mMol.getStereoBond(atom) != -1) {
int esrInfo = getESRTypeToDisplayAt(atom);
if (esrInfo != -1)
esrStr = (esrInfo == Molecule.cESRTypeAbs) ? "abs"
: (((esrInfo & 0x00FF) == Molecule.cESRTypeAnd) ? "&" : "or") + (1 + (esrInfo >> 8));
}
int hydrogensToAdd = 0;
if ((mDisplayMode & cDModeNoImplicitHydrogen) == 0) {
if (mMol.isFragment()) {
if ((mMol.getAtomQueryFeatures(atom) & Molecule.cAtomQFNoMoreNeighbours) != 0)
hydrogensToAdd = mMol.getImplicitHydrogens(atom);
}
else {
if (mMol.getAtomicNo(atom) != 6
|| mMol.getAtomMass(atom) != 0
|| !mAtomIsConnected[atom]
|| mMol.getAtomRadical(atom) != 0)
hydrogensToAdd = mMol.getImplicitHydrogens(atom);
}
}
boolean largeIsoString = false;
String atomStr = mMol.getAtomCustomLabel(atom);
if (atomStr != null && atomStr.startsWith("]")) {
isoStr = append(atomStr.substring(1), isoStr);
atomStr = null;
largeIsoString = true;
}
if (atomStr != null) {
hydrogensToAdd = 0;
}
else if (mMol.getAtomList(atom) != null) {
String atmStart = ((mMol.getAtomQueryFeatures(atom) & Molecule.cAtomQFAny) != 0) ?
"[!" : "[";
atomStr = atmStart+mMol.getAtomListString(atom)+"]";
if (atomStr.length() > 5)
atomStr = atmStart+mMol.getAtomList(atom).length+"]";
if ((mMol.getAtomQueryFeatures(atom) & Molecule.cAtomQFNoMoreNeighbours) != 0)
hydrogensToAdd = -1;
}
else if ((mMol.getAtomQueryFeatures(atom) & Molecule.cAtomQFAny) != 0) {
atomStr = "?";
if ((mMol.getAtomQueryFeatures(atom) & Molecule.cAtomQFNoMoreNeighbours) != 0)
hydrogensToAdd = -1;
}
else if (mMol.getAtomicNo(atom) != 6
|| propStr != null
|| isoStr != null
|| (hydrogensToAdd > 0)
|| !mAtomIsConnected[atom])
atomStr = mMol.getAtomLabel(atom);
double labelWidth = 0.0;
if (!mMol.isSelectedAtom(atom) & (mMol.getAtomQueryFeatures(atom) & Molecule.cAtomQFExcludeGroup) != 0)
setColorCode(COLOR_EXCLUDE_GROUP_FG);
if (atomStr != null) {
labelWidth = getStringWidth(atomStr);
mpDrawString(getAtomX(atom),getAtomY(atom),atomStr,true);
mAtomLabelDisplayed[atom] = true;
}
else if (mpAlleneCenter(atom))
mpDrawDot(getAtomX(atom),getAtomY(atom),atom);
if (propStr != null) {
mpSetSmallLabelSize();
x = getAtomX(atom) + ((labelWidth + getStringWidth(propStr)) / 2.0 + 1);
y = getAtomY(atom) - ((getTextSize()*4-4)/8);
mpDrawString(x,y,propStr,true);
mpSetNormalLabelSize();
}
if ((mDisplayMode & cDModeAtomNo) != 0)
isoStr = String.valueOf(atom);
if (isoStr != null) {
if (largeIsoString)
mpSetReducedLabelSize();
else
mpSetSmallLabelSize();
x = getAtomX(atom) - ((labelWidth + getStringWidth(isoStr)) / 2.0f);
y = getAtomY(atom) - ((getTextSize()*4-4)/8);
mpDrawString(x,y,isoStr,true);
mpSetNormalLabelSize();
}
if (cipStr != null) {
mpSetSmallLabelSize();
x = getAtomX(atom) - ((labelWidth + getStringWidth(cipStr)) / 2.0f);
y = getAtomY(atom) + ((getTextSize()*4+4)/8);
int theColor = mCurrentColor;
if (mMol.getMoleculeColor() != Molecule.cMoleculeColorNeutral && (mDisplayMode & cDModeNoColorOnESRAndCIP) == 0)
setColorCode(COLOR_CIP_LETTER);
mpDrawString(x,y,cipStr,false);
setColorCode(theColor);
mpSetNormalLabelSize();
}
if (mapStr != null) {
mpSetSmallLabelSize();
x = getAtomX(atom) + ((labelWidth + getStringWidth(mapStr)) / 2.0 + 1);
y = getAtomY(atom) + ((getTextSize()*4+4)/8);
int theColor = mCurrentColor;
setColorCode(mMol.isAutoMappedAtom(atom) ? Molecule.cAtomColorDarkGreen : Molecule.cAtomColorDarkRed);
mpDrawString(x,y,mapStr,true);
setColorCode(theColor);
mpSetNormalLabelSize();
}
if (esrStr != null) {
double angle = mpGetFreeSpaceAngle(atom);
mpSetSmallLabelSize();
x = getAtomX(atom) + 0.7*getTextSize()*Math.sin(angle);
y = getAtomY(atom) + 0.7*getTextSize()*Math.cos(angle);
int theColor = mCurrentColor;
if (!mIsValidatingView && mMol.getMoleculeColor() != Molecule.cMoleculeColorNeutral)
setColorCode(getESRColor(atom));
mpDrawString(x,y,esrStr,false);
setColorCode(theColor);
mpSetNormalLabelSize();
}
if (hydrogensToAdd == 0 && unpairedElectrons == 0) {
if (mCurrentColor == COLOR_EXCLUDE_GROUP_FG)
setColorCode(COLOR_RESTORE_PREVIOUS);
return;
}
double hindrance[] = new double[4];
for (int i=0; i 1) {
hNoStr = String.valueOf(hydrogensToAdd);
mpSetSmallLabelSize();
hNoWidth = getStringWidth(hNoStr);
}
if (hindrance[1] < 0.6 || hindrance[3] < 0.6) {
// hindrance on the left or right are reasonably small
chay = getAtomY(atom);
if (hindrance[1] <= hindrance[3]) {
hindrance[1] += 10;
chax = getAtomX(atom) + ((labelWidth + hydrogenWidth) / 2.0f);
}
else {
hindrance[3] += 10;
chax = getAtomX(atom) - ((labelWidth + hydrogenWidth) / 2.0f) - hNoWidth;
}
}
else {
chax = getAtomX(atom);
if (hindrance[0] < hindrance[2]) {
hindrance[0] += 10;
chay = getAtomY(atom) - hHeight;
}
else {
hindrance[2] += 10;
chay = getAtomY(atom) + hHeight;
}
}
if (hNoWidth > 0) {
x = chax + ((hydrogenWidth + hNoWidth) / 2.0f);
y = chay + ((getTextSize()*4+4)/8);
mpDrawString(x,y,hNoStr,true);
mpSetNormalLabelSize();
}
mpDrawString(chax,chay,"H",true);
}
int bestSide = 0;
if (unpairedElectrons != 0) {
double minHindrance = 50.0;
double counterHindrance = 0.0;
for (int i=0; i<4; i++) {
int counterSide = (i > 1) ? i - 2 : i + 2;
if (hindrance[i] < minHindrance) {
bestSide = i;
minHindrance = hindrance[i];
counterHindrance = hindrance[counterSide];
}
else if (hindrance[i] == minHindrance) {
if (hindrance[counterSide] > counterHindrance) {
bestSide = i;
counterHindrance = hindrance[counterSide];
}
}
}
switch (bestSide) {
case 0:
chax = getAtomX(atom);
chay = getAtomY(atom) - mpDotDiameter - labelWidth / 2;
break;
case 1:
chax = getAtomX(atom) + mpDotDiameter + labelWidth / 2;
chay = getAtomY(atom);
break;
case 2:
chax = getAtomX(atom);
chay = getAtomY(atom) + mpDotDiameter + labelWidth / 2;
break;
default: // 3
chax = getAtomX(atom) - mpDotDiameter - labelWidth / 2;
chay = getAtomY(atom);
break;
}
if (unpairedElectrons == 1) {
mpDrawDot(chax,chay,atom);
}
else {
switch (bestSide) {
case 0:
xdiff = 2 * mpDotDiameter;
ydiff = 0;
chax -= mpDotDiameter;
break;
case 1:
xdiff = 0;
ydiff = 2 * mpDotDiameter;
chay -= mpDotDiameter;
break;
case 2:
xdiff = 2 * mpDotDiameter;
ydiff = 0;
chax -= mpDotDiameter;
break;
default: // 3
xdiff = 0;
ydiff = 2 * mpDotDiameter;
chay -= mpDotDiameter;
break;
}
mpDrawDot(chax, chay, atom);
mpDrawDot(chax + xdiff, chay + ydiff, atom);
}
}
if (mCurrentColor == COLOR_EXCLUDE_GROUP_FG)
setColorCode(COLOR_RESTORE_PREVIOUS);
}
private String createRingSizeText(long queryFeatures) {
queryFeatures &= Molecule.cAtomQFNewRingSize;
for (int i=0; i -Math.PI*2/3 && meanAngle < Math.PI/3.0)
score -= 2*Math.cos(meanAngle + Math.PI/6.0);
else
// add a small score for angles on the top right
score -= 0.5*Math.cos(meanAngle + Math.PI/6.0);
return score;
}
private double mpGetMeanAngle(double[] angle, int index) {
if (index > 0)
return (angle[index] + angle[index-1]) / 2.0;
double mean = Math.PI + (angle[0] + angle[angle.length-1]) / 2.0;
return (mean > Math.PI) ? mean - 2.0f * Math.PI : mean;
}
private String append(String a, String b) {
return (a == null) ? b : (b == null) ? a : a+","+b;
}
private void mpDrawString(double x, double y, String str, boolean withTabu) {
if (withTabu) {
double strWidth,xdiff,ydiff;
strWidth = getStringWidth(str);
xdiff = strWidth / 2 + getTextSize() / 8;
ydiff = getTextSize() / 2;
if (str == "+" || str == "-")
ydiff = ydiff * 2 / 3;
mpTabuZone.add(new GenericRectangle(x-xdiff, y-ydiff, 2*xdiff, 2*ydiff));
}
if (!mIsValidatingView)
drawString(str,x,y);
}
private void mpDrawDot(double x, double y, int atm) {
mpTabuZone.add(new GenericRectangle(x-mpDotDiameter, y-mpDotDiameter,
2*mpDotDiameter, 2*mpDotDiameter));
if (!mIsValidatingView) {
mpDot.add(new DepictorDot(x, y, isHighlightedAtom(atm) ? COLOR_HILITE_BOND_FG : mAtomColor[atm]));
}
}
private void mpDrawAllDots() {
for (DepictorDot dot:mpDot) {
setColorCode(dot.color);
drawDot(dot.x, dot.y);
}
setColorCode(mStandardForegroundColor);
}
private boolean mpAlleneCenter(int atm) {
if (mMol.getConnAtoms(atm) != 2) return false;
for (int i=0; i<2; i++)
if (mMol.getConnBondOrder(atm,i) != 2)
return false;
return true;
}
private void mpDrawBondQueryFeatures() {
boolean textSizeChanged = false;
for (int bond=0; bond> Molecule.cBondQFRingSizeShift;
if (ringSize != 0)
label = ((label == null) ? "" : label) + ringSize;
if (label != null) {
int atom1 = mMol.getBondAtom(0, bond);
int atom2 = mMol.getBondAtom(1, bond);
if (!textSizeChanged) {
mpSetSmallLabelSize();
textSizeChanged = true;
}
double x = (getAtomX(atom1) + getAtomX(atom2)) / 2;
double y = (getAtomY(atom1) + getAtomY(atom2)) / 2;
double dx = getAtomX(atom2) - getAtomX(atom1);
double dy = getAtomY(atom2) - getAtomY(atom1);
double d = Math.sqrt(dx*dx+dy*dy);
double hw = 0.60 * getStringWidth(label); // slightly larger than 0.5f to increase label distance from bond
double hh = 0.55 * getTextSize();
if (d != 0) {
if (dx > 0)
mpDrawString(x+hw*dy/d, y-hh*dx/d, label, true);
else
mpDrawString(x-hw*dy/d, y+hh*dx/d, label, true);
}
}
}
if (textSizeChanged)
mpSetNormalLabelSize();
}
private void drawLine(DepictorLine theLine, int atom1, int atom2) {
if (mMol.isBondForegroundHilited(mMol.getBond(atom1, atom2))) {
setColorCode(COLOR_HILITE_BOND_FG);
drawBlackLine(theLine);
setColorCode(mStandardForegroundColor);
}
else if (mAtomColor[atom1] != mAtomColor[atom2]) {
drawColorLine(theLine, atom1, atom2);
}
else if (mAtomColor[atom1] != Molecule.cAtomColorNone) {
setColorCode(mAtomColor[atom1]);
drawBlackLine(theLine);
setColorCode(mStandardForegroundColor);
}
else {
drawBlackLine(theLine);
}
}
private void drawColorLine(DepictorLine theLine,int atm1,int atm2) {
DepictorLine line1 = new DepictorLine();
DepictorLine line2 = new DepictorLine();
line1.x1 = theLine.x1;
line1.y1 = theLine.y1;
line1.x2 = (theLine.x1 + theLine.x2) / 2;
line1.y2 = (theLine.y1 + theLine.y2) / 2;
line2.x1 = line1.x2;
line2.y1 = line1.y2;
line2.x2 = theLine.x2;
line2.y2 = theLine.y2;
if (mpProperLine(line1)) {
setColorCode(mAtomColor[atm1]);
drawBlackLine(line1);
}
if (mpProperLine(line2)) {
setColorCode(mAtomColor[atm2]);
drawBlackLine(line2);
}
setColorCode(mStandardForegroundColor);
}
private void drawDashedLine(DepictorLine theLine, int atom1, int atom2) {
double xinc = (theLine.x2 - theLine.x1) / 10;
double yinc = (theLine.y2 - theLine.y1) / 10;
DepictorLine aLine = new DepictorLine();
int color1,color2;
if (mMol.isBondForegroundHilited(mMol.getBond(atom1, atom2))) {
color1 = COLOR_HILITE_BOND_FG;
color2 = COLOR_HILITE_BOND_FG;
}
else {
color1 = mAtomColor[atom1];
color2 = mAtomColor[atom2];
}
setColorCode(color1);
aLine.x1 = theLine.x1;
aLine.y1 = theLine.y1;
aLine.x2 = theLine.x1 + xinc * 2;
aLine.y2 = theLine.y1 + yinc * 2;
drawBlackLine(aLine);
aLine.x1 = theLine.x1 + xinc * 4;
aLine.y1 = theLine.y1 + yinc * 4;
aLine.x2 = theLine.x1 + xinc * 5;
aLine.y2 = theLine.y1 + yinc * 5;
drawBlackLine(aLine);
setColorCode(color2);
aLine.x1 = theLine.x1 + xinc * 5;
aLine.y1 = theLine.y1 + yinc * 5;
aLine.x2 = theLine.x1 + xinc * 6;
aLine.y2 = theLine.y1 + yinc * 6;
drawBlackLine(aLine);
aLine.x1 = theLine.x1 + xinc * 8;
aLine.y1 = theLine.y1 + yinc * 8;
aLine.x2 = theLine.x2;
aLine.y2 = theLine.y2;
drawBlackLine(aLine);
setColorCode(mStandardForegroundColor);
}
private void drawShortDashedLine(DepictorLine theLine, int atom1, int atom2) {
double xdif = theLine.x2 - theLine.x1;
double ydif = theLine.y2 - theLine.y1;
double length = Math.sqrt(xdif * xdif + ydif * ydif);
int points = 2 * (int)Math.round(length / (4 * mpLineWidth));
double xinc = xdif / (points-1);
double yinc = ydif / (points-1);
int color1,color2;
if (mMol.isBondForegroundHilited(mMol.getBond(atom1, atom2))) {
color1 = COLOR_HILITE_BOND_FG;
color2 = COLOR_HILITE_BOND_FG;
}
else {
color1 = mAtomColor[atom1];
color2 = mAtomColor[atom2];
}
double x = theLine.x1 - mpLineWidth/2;
double y = theLine.y1 - mpLineWidth/2;
setColorCode(color1);
for (int i=0; i