Skip to content

Creating a minimal molecule editor

Raven edited this page Jun 20, 2018 · 1 revision

The following code is an absolute minimal implementation of a "molecule editor" (though the name is a little exaggerated given its actual functionality).

The following aspects can be found inside the code

  • Creating a molecule
  • Changing a molecule
  • Rendering a molecule

The functionality of this editor is restricted to

  • Displaying the current state of the molecule
  • Add new carbon atoms by left-clicking somewhere
  • Adding new Bonds between two atoms by dragging the mouse from the one to the other and releasing it there
  • Removing bonds or atoms by right-clicking on them
  • Highlighting the current element beneath the mouse

DISCLAIMER:
This code is certainly not the "state of the art" in terms of how things should be done within the CDK or in terms of efficiency. It can therefore be understood as a "proof of concept". If you have any enhancements, feel free to edit this page and add them in.

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;

import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.vecmath.Point2d;

import org.openscience.cdk.AtomContainer;
import org.openscience.cdk.interfaces.IAtom;
import org.openscience.cdk.interfaces.IAtomContainer;
import org.openscience.cdk.interfaces.IBond;
import org.openscience.cdk.interfaces.IChemObjectChangeEvent;
import org.openscience.cdk.interfaces.IChemObjectListener;
import org.openscience.cdk.interfaces.IBond.Order;
import org.openscience.cdk.renderer.AtomContainerRenderer;
import org.openscience.cdk.renderer.font.AWTFontManager;
import org.openscience.cdk.renderer.generators.BasicSceneGenerator;
import org.openscience.cdk.renderer.generators.IGenerator;
import org.openscience.cdk.renderer.generators.standard.StandardGenerator;
import org.openscience.cdk.renderer.visitor.AWTDrawVisitor;
import org.openscience.cdk.silent.Atom;

public class Initiator {

	public static void main(String[] args) throws InterruptedException {
		JFrame frame = new JFrame("Test here");

		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

		frame.setLocation(100, 100);
		frame.setSize(new Dimension(500, 500));

		Image image = new BufferedImage(frame.getWidth(), frame.getHeight(), BufferedImage.TYPE_INT_RGB);

		frame.add(new JLabel(new ImageIcon(image)));

		IAtomContainer myMol = new AtomContainer();
		myMol.addAtom(new Atom("C", new Point2d(1, 1)));
		myMol.addAtom(new Atom("C", new Point2d(10, 10)));
		myMol.addBond(0, 1, Order.SINGLE);
		myMol.addAtom(new Atom("N", new Point2d(-10, 20)));
		myMol.addBond(1, 2, Order.DOUBLE);

		myMol.getAtom(0).setFormalCharge(2);

		List<IGenerator<IAtomContainer>> generators = new ArrayList<IGenerator<IAtomContainer>>();
		generators.add(new BasicSceneGenerator());
		generators.add(new StandardGenerator(new JLabel().getFont()));

		Rectangle drawArea = frame.getBounds();

		// the renderer needs to have a toolkit-specific font manager
		AtomContainerRenderer renderer = new AtomContainerRenderer(generators, new AWTFontManager());
		// the call to ’setup’ only needs to be done on the first paint
		renderer.setup(myMol, drawArea);
		// paint the background
		Graphics2D g2 = (Graphics2D) image.getGraphics();
		g2.setColor(Color.WHITE);
		g2.fillRect(0, 0, drawArea.width, drawArea.height);

		renderer.setZoom(2);
		renderer.setDrawCenter(frame.getWidth() / 2, frame.getHeight() / 2);

		// the paint method also needs a toolkit-specific renderer
		// All magic happens inside the visit-method of the DrawVisitor
		AWTDrawVisitor drawVisitor = new AWTDrawVisitor(g2);
		renderer.paint(myMol, drawVisitor);

		myMol.addListener(new IChemObjectListener() {

			@Override
			public void stateChanged(IChemObjectChangeEvent event) {
				System.out.println("State changed");
			}
		});

		renderer.getRenderer2DModel().set(StandardGenerator.Highlighting.class,
				StandardGenerator.HighlightStyle.OuterGlow);


		final int maxSquareDistance = 30;

		frame.addMouseListener(new MouseAdapter() {
			Point mousePosition;

			@Override
			public void mouseReleased(MouseEvent e) {
				if (mousePosition != null && mousePosition.distanceSq(e.getPoint()) >= maxSquareDistance) {
					int atomA = -1;
					int atomB = -1;

					final Point2d startPoint = renderer.toModelCoordinates(mousePosition.getX(), mousePosition.getY());
					final Point2d endPoint = renderer.toModelCoordinates(e.getPoint().getX(), e.getPoint().getY());

					int counter = 0;
					for (IAtom currentAtom : myMol.atoms()) {
						if (currentAtom.getPoint2d().distanceSquared(startPoint) <= maxSquareDistance) {
							atomA = counter;
						} else if (currentAtom.getPoint2d().distanceSquared(endPoint) <= maxSquareDistance) {
							atomB = counter;
						}

						counter++;
					}

					if (atomA >= 0 && atomB >= 0) {
						myMol.addBond(atomA, atomB, Order.SINGLE);

						g2.setBackground(Color.WHITE);
						g2.clearRect(0, 0, drawArea.width, drawArea.height);
						g2.setBackground(Color.BLACK);

						renderer.paint(myMol, drawVisitor);
						frame.repaint();
					}
				}

				mousePosition = e.getPoint();
			}

			@Override
			public void mousePressed(MouseEvent e) {
				mousePosition = e.getPoint();
			}

			@Override
			public void mouseClicked(MouseEvent e) {
				Point2d mousePos = renderer.toModelCoordinates(e.getX(), e.getY());

				if (e.getButton() == MouseEvent.BUTTON1) {
					myMol.addAtom(new Atom("C", mousePos));
				}
				if (e.getButton() == MouseEvent.BUTTON3) {
					for (IAtom currentAtom : myMol.atoms()) {
						if (mousePos.distanceSquared(currentAtom.getPoint2d()) <= maxSquareDistance) {
							myMol.removeAtom(currentAtom);
							for (IBond bond : myMol.bonds()) {
								if (bond.contains(currentAtom)) {
									myMol.removeBond(bond);
								}
							}
						}
					}

					for (IBond currentBond : myMol.bonds()) {
						if (mousePos.distanceSquared(currentBond.get2DCenter()) <= maxSquareDistance) {
							myMol.removeBond(currentBond);
						}
					}
				}

				g2.setBackground(Color.WHITE);
				g2.clearRect(0, 0, drawArea.width, drawArea.height);
				g2.setBackground(Color.BLACK);

				renderer.paint(myMol, drawVisitor);
				frame.repaint();
			}
		});
		frame.addMouseMotionListener(new MouseMotionListener() {

			@Override
			public void mouseMoved(MouseEvent e) {
				Point2d mousePos = renderer.toModelCoordinates(e.getPoint().getX(), e.getPoint().getY());

				boolean changed = false;

				IAtom highlightedAtom = renderer.getRenderer2DModel().getHighlightedAtom();

				// unhighlight previous atom
				renderer.getRenderer2DModel().setHighlightedAtom(null);

				if (highlightedAtom != null) {
					highlightedAtom.removeProperty(StandardGenerator.HIGHLIGHT_COLOR);
				}

				boolean isHighlightingAtom = false;
				for (IAtom currentAtom : myMol.atoms()) {
					if (mousePos.distanceSquared(currentAtom.getPoint2d()) <= maxSquareDistance) {
						renderer.getRenderer2DModel().setHighlightedAtom(currentAtom);
						currentAtom.setProperty(StandardGenerator.HIGHLIGHT_COLOR, Color.RED);
						isHighlightingAtom = true;
						break;
					}
				}

				changed = !((highlightedAtom == null && !isHighlightingAtom)
						&& renderer.getRenderer2DModel().getHighlightedAtom() != null
						&& renderer.getRenderer2DModel().getHighlightedAtom().equals(highlightedAtom));

				IBond highlightedBond = renderer.getRenderer2DModel().getHighlightedBond();
				if (highlightedBond != null) {
					highlightedBond.removeProperty(StandardGenerator.HIGHLIGHT_COLOR);
				}

				// unhighlight previous bond
				renderer.getRenderer2DModel().setHighlightedBond(null);

				boolean isHighlightingBond = false;
				if (!isHighlightingAtom) {
					for (IBond currentBond : myMol.bonds()) {
						if (mousePos.distanceSquared(currentBond.get2DCenter()) <= maxSquareDistance) {
							renderer.getRenderer2DModel().setHighlightedBond(currentBond);
							currentBond.setProperty(StandardGenerator.HIGHLIGHT_COLOR, Color.RED);
							isHighlightingBond = true;
							break;
						}
					}
				}

				changed = changed || !((highlightedBond == null && !isHighlightingBond)
						&& renderer.getRenderer2DModel().getHighlightedBond() != null
						&& renderer.getRenderer2DModel().getHighlightedBond().equals(highlightedBond));

				if (changed) {
					g2.setBackground(Color.WHITE);
					g2.clearRect(0, 0, drawArea.width, drawArea.height);
					g2.setBackground(Color.BLACK);

					renderer.paint(myMol, drawVisitor);
					frame.repaint();
				}
			}

			@Override
			public void mouseDragged(MouseEvent e) {
			}
		});


		frame.setVisible(true);
	}

}