Package org.openscience.jchempaint.renderer

Source Code of org.openscience.jchempaint.renderer.AtomContainerRenderer

/*  Copyright (C) 2008-2009  Gilleain Torrance <gilleain.torrance@gmail.com>
*                2008-2009  Arvid Berg <goglepox@users.sf.net>
*                     2009  Egon Willighagen <egonw@users.sf.net>
*
*  Contact: cdk-devel@list.sourceforge.net
*
*  This program is free software; you can redistribute it and/or
*  modify it under the terms of the GNU Lesser General Public License
*  as published by the Free Software Foundation; either version 2.1
*  of the License, or (at your option) any later version.
*
*  This program is distributed in the hope that it will be useful,
*  but WITHOUT ANY WARRANTY; without even the implied warranty of
*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*  GNU Lesser General Public License for more details.
*
*  You should have received a copy of the GNU Lesser General Public License
*  along with this program; if not, write to the Free Software
*  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package org.openscience.jchempaint.renderer;

import java.awt.Rectangle;
import java.awt.geom.AffineTransform;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.List;

import javax.vecmath.Point2d;

import org.openscience.cdk.geometry.GeometryTools;
import org.openscience.cdk.interfaces.IAtom;
import org.openscience.cdk.interfaces.IAtomContainer;
import org.openscience.cdk.renderer.font.IFontManager;
import org.openscience.cdk.renderer.elements.ElementGroup;
import org.openscience.cdk.renderer.elements.IRenderingElement;
import org.openscience.jchempaint.renderer.elements.TextGroupElement;
import org.openscience.cdk.renderer.generators.IGenerator;
import org.openscience.jchempaint.renderer.visitor.IDrawVisitor;

/**
* A general renderer for {@link IAtomContainer}s. The chem object
* is converted into a 'diagram' made up of {@link IRenderingElement}s. It takes
* an {@link IDrawVisitor} to do the drawing of the generated diagram. Various
* display properties can be set using the {@link JChemPaintRendererModel}.<p>
*
* This class has several usage patterns. For just painting fit-to-screen do:
* <pre>
*   renderer.paintMolecule(molecule, visitor, drawArea)
* </pre>
* for painting at a scale determined by the bond length in the RendererModel:
* <pre>
*   if (moleculeIsNew) {
*     renderer.setup(molecule, drawArea);
*   }
*   Rectangle diagramSize = renderer.paintMolecule(molecule, visitor);
*   // ...update scroll bars here
* </pre>
* to paint at full screen size, but not resize with each change:
* <pre>
*   if (moleculeIsNew) {
*     renderer.setScale(molecule);
*     Rectangle diagramBounds = renderer.calculateDiagramBounds(molecule);
*     renderer.setZoomToFit(diagramBounds, drawArea);
*     renderer.paintMolecule(molecule, visitor);
*   } else {
*     Rectangle diagramSize = renderer.paintMolecule(molecule, visitor);
*   // ...update scroll bars here
*   }
* </pre>
* finally, if you are scrolling, and have not changed the diagram:
* <pre>
*   renderer.repaint(visitor)
* </pre>
* will just repaint the previously generated diagram, at the same scale.<p>
*
* There are two sets of methods for painting IChemObjects - those that take
* a Rectangle that represents the desired draw area, and those that return a
* Rectangle that represents the actual draw area. The first are intended for
* drawing molecules fitted to the screen (where 'screen' means any drawing
* area) while the second type of method are for drawing bonds at the length
* defined by the {@link JChemPaintRendererModel} parameter bondLength.<p>
*
* There are two numbers used to transform the model so that it fits on screen.
* The first is <tt>scale</tt>, which is used to map model coordinates to
* screen coordinates. The second is <tt>zoom</tt> which is used to, well,
* zoom the on screen coordinates. If the diagram is fit-to-screen, then the
* ratio of the bounds when drawn using bondLength and the bounds of
* the screen is used as the zoom.<p>
*
* So, if the bond length on screen is set to 40, and the average bond length
* of the model is 2 (unitless, but roughly &Aring;ngstrom scale) then the
* scale will be 20. If the model is 10 units wide, then the diagram drawn at
* 100% zoom will be 10 * 20 = 200 in width on screen. If the screen is 400
* pixels wide, then fitting it to the screen will make the zoom 200%. Since the
* zoom is just a floating point number, 100% = 1 and 200% = 2.
*
* @author maclean
* @cdk.module renderbasic
*/
public class AtomContainerRenderer implements IRenderer {
    /**
     * The default scale is used when the model is empty.
     */
    public static final double DEFAULT_SCALE = 30.0;

    protected IFontManager fontManager;

    /**
     * The renderer model is final as it is not intended to be replaced.
     */
    protected final JChemPaintRendererModel rendererModel;

    protected List<IGenerator> generators;

    protected AffineTransform transform;

    protected Point2d modelCenter = new Point2d(0, 0); // model

    protected Point2d drawCenter = new Point2d(150, 200); //diagram on screen

    protected double scale = DEFAULT_SCALE;

    protected double zoom = 1.0;

    protected IRenderingElement cachedDiagram;

    /**
     * A renderer that generates diagrams using the specified
     * generators and manages fonts with the supplied font manager.
     *
     * @param generators
     *            a list of classes that implement the IGenerator interface
     * @param fontManager
     *            a class that manages mappings between zoom and font sizes
     * @param useUserSettings Should user setting (in $HOME/.jchempaint/properties) be used or not?
     */
    public AtomContainerRenderer(List<IGenerator> generators,
                                 IFontManager fontManager) {
        rendererModel = new JChemPaintRendererModel();
        this.generators = generators;
        this.fontManager = fontManager;
        for (IGenerator<?> generator : generators) {
            if (generator.getParameters() != null)
                rendererModel.registerParameters(generator);
        }  
    }

    /**
     * Setup the transformations necessary to draw this Atom Container.
     *
     * @param atomContainer
     * @param screen
     */
    public void setup(IAtomContainer atomContainer, Rectangle screen) {
        this.setScale(atomContainer);
        Rectangle2D bounds = calculateBounds(atomContainer);
        this.modelCenter = new Point2d(bounds.getCenterX(), bounds.getCenterY());
        this.drawCenter = new Point2d(screen.getCenterX(), screen.getCenterY());
        this.setup();
    }

    public void reset() {
        modelCenter = new Point2d(0, 0);
        drawCenter = new Point2d(200, 200);
        zoom = 1.0;
        setup();
    }

    /**
     * Set the scale for an IAtomContainer. It calculates the average bond
     * length of the model and calculates the multiplication factor to transform
     * this to the bond length that is set in the RendererModel.
     * @param atomContainer
     */
    public void setScale(IAtomContainer atomContainer) {
        double bondLength = GeometryTools.getBondLengthAverage(atomContainer);
        this.scale = this.calculateScaleForBondLength(bondLength);

        // store the scale so that other components can access it
        this.rendererModel.setScale(scale);
    }

    /*
  public Rectangle paint(
      IAtomContainer atomContainer,IDrawVisitor drawVisitor) {
      // the bounds of the model
        Rectangle2D modelBounds = calculateBounds(atomContainer);

        // setup and draw
        this.setupTransformNatural(modelBounds);
        IRenderingElement diagram = this.generateDiagram(atomContainer);
        this.paint(drawVisitor, diagram);

        return this.convertToDiagramBounds(modelBounds);
  }
  */

    /**
     * Paint a molecule (an IAtomContainer).
     *
     * @param atomContainer the molecule to paint
     * @param drawVisitor the visitor that does the drawing
     * @param bounds the bounds on the screen
     * @param resetCenter
     *     if true, set the draw center to be the center of bounds
     */
    public void paintMolecule(IAtomContainer atomContainer,
                              IDrawVisitor drawVisitor, Rectangle2D bounds, boolean resetCenter) {

        // the bounds of the model
        Rectangle2D modelBounds = calculateBounds(atomContainer);

        this.setupTransformToFit(bounds, modelBounds,
                                 GeometryTools.getBondLengthAverage(atomContainer), resetCenter);

        // the diagram to draw
        IRenderingElement diagram = this.generateDiagram(atomContainer);

        this.paint(drawVisitor, diagram);
    }

    /**
     * Repaint using the cached diagram
     *
     * @param drawVisitor the wrapper for the graphics object that draws
     */
    public void repaint(IDrawVisitor drawVisitor) {
        this.paint(drawVisitor, cachedDiagram);
    }

    public Rectangle calculateDiagramBounds(IAtomContainer atomContainer) {
        return this.calculateScreenBounds(
                calculateBounds(atomContainer));
    }

    public Rectangle calculateScreenBounds(Rectangle2D modelBounds) {
        double margin = this.rendererModel.getMargin();
        Point2d modelScreenCenter
                = this.toScreenCoordinates(modelBounds.getCenterX(),
                                           modelBounds.getCenterY());
        double w = (scale * zoom * modelBounds.getWidth()) + (2 * margin);
        double h = (scale * zoom * modelBounds.getHeight()) + (2 * margin);
        return new Rectangle((int) (modelScreenCenter.x - w / 2),
                             (int) (modelScreenCenter.y - h / 2),
                             (int) w,
                             (int) h);
    }

    public static Rectangle2D calculateBounds(IAtomContainer ac) {
        // this is essential, otherwise a rectangle
        // of (+INF, -INF, +INF, -INF) is returned!
        if (ac.getAtomCount() == 0) {
            return new Rectangle2D.Double();
        }
        else if (ac.getAtomCount() == 1) {
            Point2d p = ac.getAtom(0).getPoint2d();
            return new Rectangle2D.Double(p.x, p.y, 0, 0);
        }

      double xmin = Double.POSITIVE_INFINITY;
      double xmax = Double.NEGATIVE_INFINITY;
      double ymin = Double.POSITIVE_INFINITY;
      double ymax = Double.NEGATIVE_INFINITY;

      for (IAtom atom : ac.atoms()) {
        Point2d p = atom.getPoint2d();
        xmin = Math.min(xmin, p.x);
        xmax = Math.max(xmax, p.x);
        ymin = Math.min(ymin, p.y);
        ymax = Math.max(ymax, p.y);
      }
      double w = xmax - xmin;
      double h = ymax - ymin;
      return new Rectangle2D.Double(xmin, ymin, w, h);
    }

    public JChemPaintRendererModel getRenderer2DModel() {
    return this.rendererModel;
  }

    public Point2d toModelCoordinates(double screenX, double screenY) {
        try {
            double[] dest = new double[2];
            double[] src = new double[] { screenX, screenY };
            transform.inverseTransform(src, 0, dest, 0, 1);
            return new Point2d(dest[0], dest[1]);
        } catch (NoninvertibleTransformException n) {
            return new Point2d(0,0);
        }
    }

    public Point2d toScreenCoordinates(double modelX, double modelY) {
        double[] dest = new double[2];
        transform.transform(new double[] { modelX, modelY }, 0, dest, 0, 1);
        return new Point2d(dest[0], dest[1]);
    }

    public void setModelCenter(double x, double y) {
      this.modelCenter = new Point2d(x, y);
      setup();
  }

  public void setDrawCenter(double x, double y) {
        this.drawCenter = new Point2d(x, y);
        setup();
    }

  public void setZoom(double z) {
      getRenderer2DModel().setZoomFactor( z );
      zoom = z;
      setup();
  }

    /**
     * Move the draw center by dx and dy.
     *
     * @param dx
     *            the x shift
     * @param dy
     *            the y shift
     */
  public void shiftDrawCenter(double dx, double dy) {
      this.drawCenter.set(this.drawCenter.x + dx, this.drawCenter.y + dy);
      setup();
  }

  public Point2d getDrawCenter() {
        return drawCenter;
    }

    public Point2d getModelCenter() {
        return modelCenter;
    }

    /**
     * Calculate and set the zoom factor needed to completely fit the diagram
     * onto the screen bounds.
     *
     * @param diagramBounds
     * @param drawBounds
     */
    public void setZoomToFit(double drawWidth,
                             double drawHeight,
                             double diagramWidth,
                             double diagramHeight) {

        double m = this.rendererModel.getMargin();

        // determine the zoom needed to fit the diagram to the screen
        double widthRatio  = drawWidth  / (diagramWidth  + (2 * m));
        double heightRatio = drawHeight / (diagramHeight + (2 * m));

        this.zoom = Math.min(widthRatio, heightRatio);

        this.fontManager.setFontForZoom(zoom);

        // record the zoom in the model, so that generators can use it
        this.rendererModel.setZoomFactor(zoom);

    }

    /**
     * The target method for paintChemModel, paintReaction, and paintMolecule.
     *
     * @param drawVisitor
     *            the visitor to draw with
     * @param diagram
     *            the IRenderingElement tree to render
     */
  private void paint(IDrawVisitor drawVisitor,
                     IRenderingElement diagram) {
      if (diagram == null) return;

      // cache the diagram for quick-redraw
      this.cachedDiagram = diagram;

      this.fontManager.setFontName(this.rendererModel.getFontName());
      this.fontManager.setFontStyle(this.rendererModel.getFontStyle());

      drawVisitor.setFontManager(this.fontManager);
      drawVisitor.setTransform(this.transform);
      drawVisitor.setRendererModel(this.rendererModel);
      diagram.accept(drawVisitor);
  }

  /**
   * Set the transform for a non-fit to screen paint.
   *
   * @param modelBounds
     *            the bounding box of the model
   */
  private void setupTransformNatural(Rectangle2D modelBounds) {
      this.zoom = this.rendererModel.getZoomFactor();
        this.fontManager.setFontForZoom(zoom);
        this.setup();
  }

    /**
     * Sets the transformation needed to draw the model on the canvas when
     * the diagram needs to fit the screen.
     *
     * @param screenBounds
     *            the bounding box of the draw area
     * @param modelBounds
     *            the bounding box of the model
     * @param bondLength
     *            the average bond length of the model
     * @param reset
     *            if true, model center will be set to the modelBounds center
     *            and the scale will be re-calculated
     */
  private void setupTransformToFit(Rectangle2D screenBounds,
                              Rectangle2D modelBounds,
                              double bondLength,
                              boolean reset) {

      if (screenBounds == null) return;

        this.setDrawCenter(
                screenBounds.getCenterX(), screenBounds.getCenterY());

        this.scale = this.calculateScaleForBondLength(bondLength);

        double drawWidth = screenBounds.getWidth();
        double drawHeight = screenBounds.getHeight();

        double diagramWidth = modelBounds.getWidth() * scale;
        double diagramHeight = modelBounds.getHeight() * scale;

        this.setZoomToFit(drawWidth, drawHeight, diagramWidth, diagramHeight);

      // this controls whether editing a molecule causes it to re-center
      // with each change or not
      if (reset || rendererModel.isFitToScreen()) {
            this.setModelCenter(
                    modelBounds.getCenterX(), modelBounds.getCenterY());
        }

      // set the scale in the renderer model for the generators
      if (reset) {
          this.rendererModel.setScale(scale);
      }

      this.setup();
  }

  /**
   * Given a bond length for a model, calculate the scale that will transform
   * this length to the on screen bond length in RendererModel.
   *
   * @param modelBondLength
   * @param reset
   * @return
   */
  private double calculateScaleForBondLength(double modelBondLength) {
      if (Double.isNaN(modelBondLength) || modelBondLength == 0) {
            return DEFAULT_SCALE;
        } else {
            return this.rendererModel.getBondLength() / modelBondLength;
        }
  }

    /**
     * Calculate the bounds of the diagram on screen, given the current scale,
     * zoom, and margin.
     *
     * @param modelBounds
     *            the bounds in model space of the chem object
     * @return the bounds in screen space of the drawn diagram
     */
  private Rectangle convertToDiagramBounds(Rectangle2D modelBounds) {
      double cx = modelBounds.getCenterX();
        double cy = modelBounds.getCenterY();
        double mw = modelBounds.getWidth();
        double mh = modelBounds.getHeight();

        Point2d mc = this.toScreenCoordinates(cx, cy);

        // special case for 0 or 1 atoms
        if (mw == 0 && mh == 0) {
            return new Rectangle((int)mc.x, (int)mc.y, 0, 0);
        }

        double margin = this.rendererModel.getMargin();
        int w = (int) ((scale * zoom * mw) + (2 * margin));
        int h = (int) ((scale * zoom * mh) + (2 * margin));
        int x = (int) (mc.x - w / 2);
        int y = (int) (mc.y - h / 2);

        return new Rectangle(x, y, w, h);
  }

  private void setup() {

        // set the transform
        try {
            this.transform = new AffineTransform();
            this.transform.translate(this.drawCenter.x, this.drawCenter.y);
            this.transform.scale(1,-1); // Converts between CDK Y-up & Java2D Y-down coordinate-systems
            this.transform.scale(this.scale, this.scale);
            this.transform.scale(this.zoom, this.zoom);
            this.transform.translate(-this.modelCenter.x, -this.modelCenter.y);
        } catch (NullPointerException npe) {
            System.err.println(String.format(
                    "null pointer when setting transform: " +
                    "drawCenter=%s scale=%s zoom=%s modelCenter=%s",
                    this.drawCenter,
                    this.scale,
                    this.zoom,
                    this.modelCenter));
        }
  }

  protected IRenderingElement generateDiagram(IAtomContainer ac) {
      ElementGroup diagram = new ElementGroup();
        for (IGenerator generator : this.generators) {
          IRenderingElement element = generator.generate(ac, this.rendererModel);
          if(!(element instanceof TextGroupElement) || ((TextGroupElement)element).children.size()>0)
            diagram.add(element);
        }
        //Rgroup stuff (not contained in the ac)
       
        return diagram;
  }

  public List<IGenerator> getGenerators(){
      return new ArrayList<IGenerator>(generators);
  }

  /**
   * Gets the currently used FontManager.
   *
   * @return The currently used FontManager.
   */
  public IFontManager getFontManager() {
    return fontManager;
  }
}
TOP

Related Classes of org.openscience.jchempaint.renderer.AtomContainerRenderer

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.