/** * Squidy Interaction Library 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 3 of the License, * or (at your option) any later version. * * Squidy Interaction Library 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 Squidy Interaction Library. If not, see * <http://www.gnu.org/licenses/>. * * 2009 Human-Computer Interaction Group, University of Konstanz. * <http://hci.uni-konstanz.de> * * Please contact info@squidy-lib.de or visit our website * <http://www.squidy-lib.de> for further information. */ package org.squidy.designer.component; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics2D; import java.awt.Image; import java.awt.Rectangle; import java.awt.Shape; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.awt.geom.RoundRectangle2D; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import org.squidy.designer.constant.Constants; import org.squidy.designer.shape.VisualShape; import org.squidy.designer.util.AffineTransformUtils; import org.squidy.designer.util.StrokeUtils; import edu.umd.cs.piccolo.PNode; import edu.umd.cs.piccolo.event.PBasicInputEventHandler; import edu.umd.cs.piccolo.event.PDragEventHandler; import edu.umd.cs.piccolo.event.PInputEvent; import edu.umd.cs.piccolo.util.PAffineTransform; import edu.umd.cs.piccolo.util.PBounds; import edu.umd.cs.piccolo.util.PDimension; import edu.umd.cs.piccolo.util.PPaintContext; import edu.umd.cs.piccolo.util.PPickPath; /** * <code>CropScroll</code>. * * <pre> * Date: Mar 27, 2009 * Time: 8:30:17 PM * </pre> * * @author <pre> * Daniel Weidele * <a href="mailto:Daniel.Weidele@uni-konstanz.de">Daniel.Weidele@uni-konstanz.de</a> * Human-Computer Interaction Group * University of Konstanz * </pre> * @author <pre> * Roman R&aumldle * <a href="mailto:Roman.Raedle@uni-konstanz.de">Roman.Raedle@uni-konstanz.de</a> * Human-Computer Interaction Group * University of Konstanz * </pre> * * @version $Id: CropScroll.java 772 2011-09-16 15:39:44Z raedle $ * @since 1.0.0 */ public class CropScroll extends VisualShape<VisualShape<?>> { /** * Generated serial version UID. */ private static final long serialVersionUID = 3112648133701151505L; private static final int CROP_SCROLLER_GAP = 30; public static final String CROP_SCROLLER_UPDATE = "CROP_SCROLLER_UPDATE"; private CropScroller cropScroller; private PNode cropNode; private Dimension viewPort; private Image scrollerImage; private Image previousScrollerImage; private double scaleY = 1.0; private PAffineTransform scaleScrollerYTransform; // private double scaleY = 1.0; private boolean fitWidth = true; public CropScroll(final PNode cropNode, final Dimension viewPort, final double scrollerScale, boolean fitWidth) { this (cropNode, viewPort, scrollerScale); this.fitWidth = fitWidth; } public CropScroll(final PNode cropNode, final Dimension viewPort, final double scrollerScale) { this.cropNode = cropNode; this.viewPort = viewPort; addChild(cropNode); // PBounds cropNodeBounds = cropNode.getFullBoundsReference(); // if (cropNodeBounds.getHeight() > viewPort.getHeight() / // scrollerScale) { // scaleY = viewPort.getHeight() / cropNodeBounds.getHeight(); // } // cropNode.setPickable(false); // Create the crop scroller. cropScroller = new CropScroller(); cropScroller.setScale(scrollerScale); addChild(cropScroller); cropNode.addPropertyChangeListener(PNode.PROPERTY_BOUNDS, new PropertyChangeListener() { /* * (non-Javadoc) * * @seejava.beans.PropertyChangeListener#propertyChange(java.beans. * PropertyChangeEvent) */ public void propertyChange(PropertyChangeEvent evt) { cropScroller.updateBounds(); } }); cropNode.addPropertyChangeListener(PNode.PROPERTY_FULL_BOUNDS, new PropertyChangeListener() { private boolean scaled = false; /* * (non-Javadoc) * * @seejava.beans.PropertyChangeListener#propertyChange(java.beans. * PropertyChangeEvent) */ public void propertyChange(PropertyChangeEvent evt) { // TODO [RR]: Improve performance -> will be called each // scrolling if (!fitWidth) { return; } PBounds cropNodeFullBounds = (PBounds) evt.getNewValue(); double scaleY = viewPort.getWidth() / cropNodeFullBounds.getWidth(); if (!scaled) { scaled = true; cropNode.setScale(scaleY); // PBounds cropNodeBounds = cropNode.getFullBoundsReference(); // if (cropNodeBounds.getHeight() * scaleY > viewPort.getHeight() / scrollerScale) { // CropScroll.this.scaleY = viewPort.getHeight() / cropNodeBounds.getHeight(); // } //System.out.println("SCALEY: " + scaleY); cropScroller.updateBounds(); } } }); // Add property change listener to allow updates of crop scroller from // other components in component hierarchy. cropNode.addPropertyChangeListener(CROP_SCROLLER_UPDATE, new PropertyChangeListener() { /* * (non-Javadoc) * * @seejava.beans.PropertyChangeListener#propertyChange(java.beans. * PropertyChangeEvent) */ public void propertyChange(PropertyChangeEvent evt) { previousScrollerImage = scrollerImage; scrollerImage = null; invalidatePaint(); } }); setBounds(0, 0, viewPort.getWidth() + (viewPort.getWidth() * scrollerScale) + CROP_SCROLLER_GAP, viewPort .getHeight()); cropScroller.updateBounds(); } /* * (non-Javadoc) * * @see * org.squidy.designer.VisualShape#paintShape(edu.umd.cs.piccolo. * util.PPaintContext) */ protected void paintShape(PPaintContext paintContext) { super.paintShape(paintContext); // cropNode.toImage(scrollerImage, Color.WHITE); // if (scrollerImage == null && cropNode.getWidth() > 0 && cropNode.getHeight() > 0 PBounds fullBounds = cropNode.getFullBoundsReference(); if (scrollerImage == null && fullBounds.getWidth() > 0 && fullBounds.getHeight() > 0) { scrollerImage = cropNode.toImage(); scaleScrollerYTransform = null; } Graphics2D g = paintContext.getGraphics(); g.clip(getBoundsReference()); } /* * (non-Javadoc) * * @seeedu.umd.cs.piccolo.PNode#paintAfterChildren(edu.umd.cs.piccolo.util. * PPaintContext) */ @Override protected void paintAfterChildren(PPaintContext paintContext) { super.paintAfterChildren(paintContext); Graphics2D g = paintContext.getGraphics(); g.setClip(null); g.setColor(Color.LIGHT_GRAY); g.setStroke(new BasicStroke(0.3f)); PBounds cropNodeBounds = cropNode.getFullBoundsReference(); PBounds bounds = getBoundsReference(); if (isRenderPrimitiveRect()) g.drawRect((int) bounds.getX(), (int) bounds.getY(), (int) cropNodeBounds.getWidth(), (int) bounds.getHeight()); else g.drawRoundRect((int) bounds.getX(), (int) bounds.getY(), (int) cropNodeBounds.getWidth(), (int) bounds.getHeight(), 2, 2); // g.setColor(Color.RED); // g.draw(getBoundsReference()); // // paintContext.pushTransform(cropNode.getTransform()); // g.setColor(Color.GREEN); // g.draw(cropNode.getBoundsReference()); // paintContext.popTransform(cropNode.getTransform()); // // paintContext.pushTransform(cropScroller.getTransform()); // g.setColor(Color.PINK); // g.draw(cropScroller.getBoundsReference()); // paintContext.popTransform(cropScroller.getTransform()); } /* * (non-Javadoc) * * @see edu.umd.cs.piccolo.PNode#fullPick(edu.umd.cs.piccolo.util.PPickPath) */ @Override public boolean fullPick(PPickPath pickPath) { Point2D offset = cropNode.getOffset(); PBounds pickBounds = pickPath.getPickBounds(); double x = pickBounds.getX() + offset.getX(); double y = pickBounds.getY() + offset.getY(); double width = pickBounds.getWidth(); double height = pickBounds.getHeight(); if ((getPickable() || getChildrenPickable()) && getFullBoundsReference().intersects(x, y, width, height)) { pickPath.pushNode(this); pickPath.pushTransform(getTransform()); Point2D offset2 = cropScroller.getOffset(); PBounds movedBounds = cropScroller.getBounds(); movedBounds = cropScroller.getBounds().moveBy(offset2.getX(), offset2.getY()); if (/* cropScroller.fullIntersects(pickBounds) */pickBounds.intersects(movedBounds)) { pickPath.pushNode(cropScroller); pickPath.pushTransform(cropScroller.getTransform()); } else if (cropNode.fullPick(pickPath)) { return true; } return true; } return false; } // ############################################################################# // BEGIN INNER CLASSES // ############################################################################# /** * <code>CropScroller</code>. * * <pre> * Date: Mar 27, 2009 * Time: 8:26:49 PM * </pre> * * @author <pre> * Daniel Weidele * <a href="mailto:Daniel.Weidele@uni-konstanz.de">Daniel.Weidele@uni-konstanz.de</a> * Human-Computer Interaction Group * University of Konstanz * </pre> * @author <pre> * Roman R&aumldle * <a href="mailto:Roman.Raedle@uni-konstanz.de">Roman.Raedle@uni-konstanz.de</a> * Human-Computer Interaction Group * University of Konstanz * </pre> * * @version $Id: CropScroll.java 772 2011-09-16 15:39:44Z raedle $ * @since 1.0.0 */ private class CropScroller extends VisualShape<VisualShape<?>> { private static final long serialVersionUID = 7634902132303924117L; private Shape scrollerOverlay; private PAffineTransform scrollerOverlayTranslation = new PAffineTransform(); /** * @param cropNode */ public CropScroller() { PBasicInputEventHandler mouseWheelScrollHandler = new PBasicInputEventHandler() { @Override public void mouseWheelRotated(PInputEvent event) { super.mouseWheelRotated(event); scrollByDelta(event.getWheelRotation() * 10); event.setHandled(true); } }; cropNode.addInputEventListener(mouseWheelScrollHandler); addInputEventListener(mouseWheelScrollHandler); addInputEventListener(new PDragEventHandler() { /* * (non-Javadoc) * * @see * edu.umd.cs.piccolo.event.PDragEventHandler#drag(edu.umd.cs * .piccolo.event.PInputEvent) */ @Override protected void drag(PInputEvent event) { if (!event.isHandled() && provideScrolling()) { PDimension delta = event.getDelta(); cropScroller.globalToLocal(delta); double deltaY = delta.getHeight() / scaleY; scrollByDelta(deltaY); event.setHandled(true); } } }); } /** * @param deltaY */ private void scrollByDelta(double deltaY) { PBounds cropNodeBounds = cropNode.getFullBoundsReference(); Point2D cropNodeOffset = cropNode.getOffset(); double offsetY = cropNodeOffset.getY() - deltaY; if (offsetY > 0) { offsetY = 0; } else if (cropNodeBounds.getHeight() + cropNodeOffset.getY() - CropScroll.this.getHeight() - deltaY < 0) { offsetY = -(cropNodeBounds.getHeight() - CropScroll.this.getHeight()); } cropNode.setOffset(0, offsetY); invalidatePaint(); } /** * */ public void updateBounds() { previousScrollerImage = scrollerImage; scrollerImage = null; setBounds(cropNode.getFullBounds()); cropScroller.setOffset(viewPort.getWidth() + CROP_SCROLLER_GAP, 0); } /* * (non-Javadoc) * * @see * org.squidy.designer.VisualShape#paintShape(edu.umd.cs.piccolo * .util.PPaintContext) */ @Override protected void paintShape(PPaintContext paintContext) { super.paintShape(paintContext); if (scrollerImage == null) { scrollerImage = previousScrollerImage; previousScrollerImage = null; } // Scale crop scroller to fit in y dimension. if (scaleScrollerYTransform == null) { int imageHeight = scrollerImage.getHeight(null); scaleY = (CropScroll.this.getHeight() / CropScroller.this.getScale()) / imageHeight; scaleScrollerYTransform = AffineTransformUtils.getScaleInstance(1.0, scaleY < 1 ? scaleY : 1.0); } paintContext.pushTransform(scaleScrollerYTransform); Rectangle2D cropScrollBounds = CropScroll.this.getBoundsReference(); int x = (int) cropScrollBounds.getX(); int y = (int) cropScrollBounds.getY(); int width = viewPort.width; int height = viewPort.height; Graphics2D g = paintContext.getGraphics(); if (!CropScroll.this.isRenderPrimitive()) { //cropNode.fullPaint(paintContext); g.drawImage(scrollerImage, 0, 0, null); } Point2D offset = cropNode.getOffset(); scrollerOverlayTranslation.setToTranslation(-offset.getX(), -offset.getY()); paintContext.pushTransform(scrollerOverlayTranslation); if (provideScrolling()) { g.setStroke(StrokeUtils.getBasicStroke(3.5f)); // g.setColor(Color.GRAY); // g.drawRoundRect(x - 3, y, width + 6, height, 10, 10); // g.setColor(new Color(200, 200, 200, 100)); // g.fillRoundRect(x - 3, y, width + 6, height, 10, 10); if (scrollerOverlay == null) { scrollerOverlay = new RoundRectangle2D.Double(x, y, width, height, 20, 20); } Rectangle bounds = scrollerOverlay.getBounds(); g.setColor(Constants.Color.COLOR_SHAPE_BACKGROUND); if (isRenderPrimitiveRect()) g.fillRect(bounds.x, bounds.y, bounds.width, bounds.height); else g.fill(scrollerOverlay); g.setColor(Color.GRAY); if (isRenderPrimitiveRect()) g.drawRect(bounds.x, bounds.y, bounds.width, bounds.height); else g.draw(scrollerOverlay); } paintContext.popTransform(scrollerOverlayTranslation); // Scale crop scroller to fit in y dimension. paintContext.popTransform(scaleScrollerYTransform); // Rectangle2D cropScrollBounds = CropScroll.this.getBoundsReference(); // int x = (int) cropScrollBounds.getX(); // int y = (int) cropScrollBounds.getY(); // int width = viewPort.width; // int height = viewPort.height; // // Graphics2D g = paintContext.getGraphics(); // // Point2D offset = cropNode.getOffset(); // PAffineTransform translation = new PAffineTransform(); // translation.translate(-offset.getX() * scaleY, -offset.getY() * scaleY); // paintContext.pushTransform(translation); // // PAffineTransform transform2 = new PAffineTransform(); // transform2.scale(1.0, scaleY); // paintContext.pushTransform(transform2); // //// paintContext.pushTransform(TRANSFORM); // if (!CropScroll.this.isRenderPrimitiv()) { // cropNode.fullPaint(paintContext); // } //// paintContext.popTransform(TRANSFORM); // // if (provideScrolling()) { // g.setStroke(new BasicStroke(0.5f)); // // // g.setColor(Color.GRAY); // // g.drawRoundRect(x - 3, y, width + 6, height, 10, 10); // // g.setColor(new Color(200, 200, 200, 100)); // // g.fillRoundRect(x - 3, y, width + 6, height, 10, 10); // // g.setColor(Color.BLACK); // g.drawRoundRect(1 + x, y, width - 2, height, 2, 2); // g.setColor(new Color(200, 200, 200, 100)); // g.fillRoundRect(x, y, width, height, 2, 2); // } // // paintContext.popTransform(transform2); // // paintContext.popTransform(translation); } } /** * Whether scrolling should be provided or not. * * @return */ private boolean provideScrolling() { return viewPort.getHeight() < cropNode.getFullBoundsReference().getHeight(); } // ############################################################################# // END INNER CLASSES // ############################################################################# }