All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.actelion.research.chem.AbstractDepictor Maven / Gradle / Ivy

There is a newer version: 2024.11.2
Show newest version
/*
 * 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




© 2015 - 2024 Weber Informatics LLC | Privacy Policy