// PathVisio, // a tool for data visualization and analysis using Biological Pathways // Copyright 2006-2011 BiGCaT Bioinformatics // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // package org.pathvisio.core.view; import java.awt.Color; import java.awt.Graphics2D; import java.awt.Shape; import java.awt.geom.AffineTransform; import java.awt.geom.Ellipse2D; import java.awt.geom.Rectangle2D; /** * A Handle is a little marker (like a little * yellow square) that the user can grab with the mouse * and drag around, and in that way adjust some property * of an object (such as its width, rotation, etc.) */ public class Handle extends VPathwayElement { //The direction this handle is allowed to move in final private Freedom freedom; /** * Freedom determines the freedom of movement of * a Handle. The freedom of a Handle is * specified at creation time and does not change during * the life of a Handle. */ public enum Freedom { /** a FREE handle can move to any location on the canvas. When shift is pressed, it maintains aspect ratio of the shape */ FREE, /** a FREER handle can move to any location on the canvas, it move diagonally (perpendicular to XY) when SHIFT is pressed */ NEGFREE, /** an X handle can only move horizontally */ X, /** an Y handle can only move vertically */ Y, /** a ROTATION handle can only move in a circle with a fixed radius */ ROTATION, }; // Typical size of handle, in pixels private static final int WIDTH = 8; private static final int HEIGHT = 8; /** * Style determines the visual appearance of a Handle */ enum Style { /** * The default appearance: a yellow square, * suitable for adjusting width / height / scale of an object */ DEFAULT, /** Appearance for handles on line segments, a blue diamond */ SEGMENT, /** Appearance for rotation handles, a green circle */ ROTATE, /** Invisible handles are on the corners of a selectionbox */ INVISIBLE, }; private Style style = Style.DEFAULT; final private Adjustable adjustable; final private VPathwayElement parent; // location of this handle private double mCenterx; private double mCentery; // used for calculations related to rotating parent objects double rotation; // the start angle of the handle without rotation // (degrees of the angle in the bounding box of the element) private int angle = 1; /** * Constructor for this class, creates a handle given the parent, direction and canvas * @param aFreedom Direction this handle can be moved in (one of the Freedom enum) * @param parent The {@link VPathwayElement} this handle belongs to, and which * will be selected when this handle is clicked * @param adjustable The object that is being adjusted by this handle. This is usually, * but not always, the same as parent. For example, a Handle on a {@link VPoint} * has a {@link Line} as parent but the {@link VPoint} as adjustable */ public Handle(Freedom aFreedom, VPathwayElement parent, Adjustable adjustable) { super(parent.canvas); freedom = aFreedom; this.adjustable = adjustable; this.parent = parent; if(freedom == Freedom.ROTATION) { setStyle(Style.ROTATE); } } /** * Set the appearance style of the handle. * @param style One of the styles defined in {@link Style} */ public void setStyle(Style style) { this.style = style; } /** * set the angle of the handle * is used to calculate the corresponding cursor type * @param angle without rotation in degrees */ public void setAngle(int angle) { this.angle = angle; } public int getAngle() { return angle; } /** * The object being adjusted by this Handle. Usually, but * not always, the same as the Parent */ public Adjustable getAdjustable() { return adjustable; } /** * The parent of this Handle, this is the object that this Handle is * near, and will be selected when clicking this Handle. */ public VPathwayElement getParent() { return parent; } /** * Get the type of freedom of movement that this handle has * @return one of {@link Freedom} */ public Freedom getFreedom() { return freedom; } /** Set the handle location in view coordinates */ public void setVLocation(double vx, double vy) { markDirty(); mCenterx = mFromV(vx); mCentery = mFromV(vy); markDirty(); } /** Set the handle location in model coordinates */ public void setMLocation(double mx, double my) { markDirty(); mCenterx = mx; mCentery = my; markDirty(); } /** get the center x in view coordinates */ public double getVCenterX() { return vFromM(mCenterx); } /** get the center y in view coordinates */ public double getVCenterY() { return vFromM(mCentery); } /** * Draws itself, the look depends on style. If * the style is Style.INVISIBLE, nothing is drawn at all */ public void doDraw(Graphics2D g) { if(style == Style.INVISIBLE) return; // nothing to draw Shape fillShape = getFillShape(); switch(style) { case ROTATE: g.setColor(Color.GREEN); break; case SEGMENT: g.setColor(new Color(0, 128, 255)); break; default: g.setColor(Color.YELLOW); break; } g.fill(fillShape); g.setColor(Color.BLACK); g.draw(fillShape); } /** Note: don't use Handle.vMoveBy, use vMoveTo instead. it's impossible to handle snap-to-grid correctly if you only have the delta information. */ public void vMoveBy(double vdx, double vdy) { assert (false); // You shouldn't call vMoveBy on a handle! use vMoveTo instead } /** Called when a mouse event forces the handle to move. Note: this doesn't cause the handle itself to move, rather, it passes the information to the underlying {@link Adjustable} It is the responsibility of the {@link Adjustable} to update the position of this Handle again. */ public void vMoveTo(double vnx, double vny) { adjustable.adjustToHandle(this, vnx, vny); } public Shape calculateVOutline() { return getFillShape((int)Math.ceil(DEFAULT_STROKE.getLineWidth())).getBounds(); } private Shape getFillShape() { return getFillShape(0); } /** get the FillShape * @param sw the stroke width */ private Shape getFillShape(int sw) { Shape s = null; switch(style) { case ROTATE: s = new Ellipse2D.Double(getVCenterX() - WIDTH/2, getVCenterY() - HEIGHT/2, WIDTH + sw, HEIGHT + sw); break; case SEGMENT: s = new Rectangle2D.Double(getVCenterX() - WIDTH/2, getVCenterY() - HEIGHT/2, WIDTH + sw, HEIGHT + sw); s = AffineTransform.getRotateInstance( Math.PI / 4, getVCenterX(), getVCenterY() ).createTransformedShape(s); break; default: s = new Rectangle2D.Double(getVCenterX() - WIDTH/2, getVCenterY() - HEIGHT/2, WIDTH + sw, HEIGHT + sw); break; } return s; } /** prints some extra debug info */ public String toString() { return "Handle with parent: " + adjustable.toString() + " and direction " + freedom; } protected int getZOrder() { return VPathway.ZORDER_HANDLE; } }