/* * Copyright (C) 2010 Brockmann Consult GmbH (info@brockmann-consult.de) * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 3 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 General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, see http://www.gnu.org/licenses/ */ package com.bc.ceres.grender.support; import com.bc.ceres.core.Assert; import com.bc.ceres.grender.Viewport; import com.bc.ceres.grender.ViewportListener; import java.awt.*; import java.awt.geom.AffineTransform; import java.awt.geom.NoninvertibleTransformException; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.util.ArrayList; public class DefaultViewport implements Viewport { private Rectangle viewBounds; private AffineTransform modelToViewTransform; private AffineTransform viewToModelTransform; private boolean modelYAxisDown; private double orientation; private ArrayList<ViewportListener> changeListeners; public DefaultViewport() { this(new Rectangle()); } public DefaultViewport(Rectangle viewBounds) { this(viewBounds, false); } public DefaultViewport(boolean modelYAxisDown) { this(new Rectangle(), modelYAxisDown); } public DefaultViewport(Rectangle viewBounds, boolean modelYAxisDown) { Assert.notNull(viewBounds, "viewBounds"); this.viewBounds = new Rectangle(viewBounds); this.modelYAxisDown = modelYAxisDown; this.modelToViewTransform = AffineTransform.getScaleInstance(1.0, modelYAxisDown ? 1.0 : -1.0); this.viewToModelTransform = AffineTransform.getScaleInstance(1.0, modelYAxisDown ? 1.0 : -1.0); this.changeListeners = new ArrayList<>(3); } @Override public boolean isModelYAxisDown() { return modelYAxisDown; } @Override public void setModelYAxisDown(boolean modelYAxisDown) { if (this.modelYAxisDown != modelYAxisDown) { this.modelYAxisDown = modelYAxisDown; viewToModelTransform.scale(1.0, -1.0); modelToViewTransform.scale(1.0, -1.0); fireViewportChanged(false); } } @Override public Rectangle getViewBounds() { return new Rectangle(viewBounds); } @Override public void setViewBounds(Rectangle viewBounds) { Assert.notNull(viewBounds, "viewBounds"); if (!viewBounds.equals(this.viewBounds)) { this.viewBounds.setRect(viewBounds); fireViewportChanged(false); } } @Override public AffineTransform getViewToModelTransform() { return new AffineTransform(viewToModelTransform); } @Override public AffineTransform getModelToViewTransform() { return new AffineTransform(modelToViewTransform); } @Override public double getOrientation() { return orientation; } @Override public void setOrientation(double orientation) { Point2D.Double vc = getViewportCenterPoint(); setOrientation(orientation, vc); } @Override public double getOffsetX() { return viewToModelTransform.getTranslateX(); } @Override public double getOffsetY() { return viewToModelTransform.getTranslateY(); } @Override public void setOffset(double offsetX, double offsetY) { viewToModelTransform.setTransform(viewToModelTransform.getScaleX(), viewToModelTransform.getShearY(), viewToModelTransform.getShearX(), viewToModelTransform.getScaleY(), offsetX, offsetY); updateModelToViewTransform(); fireViewportChanged(false); } @Override public void moveViewDelta(double deltaX, double deltaY) { if (deltaX == 0.0 && deltaY == 0.0) { return; } viewToModelTransform.translate(-deltaX, -deltaY); updateModelToViewTransform(); fireViewportChanged(false); } @Override public double getZoomFactor() { return Math.sqrt(Math.abs(modelToViewTransform.getDeterminant())); } @Override public void setZoomFactor(double zoomFactor) { Assert.argument(zoomFactor > 0.0, "zoomFactor > 0.0"); setZoomFactor(zoomFactor, getViewportCenterPoint()); } @Override public void zoom(Rectangle2D modelBounds) { final double viewportWidth = viewBounds.width; final double viewportHeight = viewBounds.height; final double zoomFactor = Math.min(viewportWidth / modelBounds.getWidth(), viewportHeight / modelBounds.getHeight()); if (zoomFactor > 0.0) { setZoomFactor(zoomFactor, modelBounds.getCenterX(), modelBounds.getCenterY()); // // useful for debugging - don't delete // System.out.println(this.getClass() + " ==============================================="); // System.out.println(" modelBounds = " + modelBounds); // System.out.println(" computedModelBounds = " + getViewToModelTransform().createTransformedShape(viewBounds).getBounds2D()); // System.out.println(" viewBounds = " + viewBounds); // System.out.println(" computedViewBounds = " + getModelToViewTransform().createTransformedShape(modelBounds).getBounds2D()); } } @Override public void setZoomFactor(double zoomFactor, double modelCenterX, double modelCenterY) { Assert.argument(zoomFactor > 0.0, "zoomFactor > 0.0"); final double sx = 1.0; final double sy = modelYAxisDown ? 1.0 : -1.0; final double viewportWidth = viewBounds.width; final double viewportHeight = viewBounds.height; final double modelOffsetX = modelCenterX - 0.5 * sx * viewportWidth / zoomFactor; final double modelOffsetY = modelCenterY - 0.5 * sy * viewportHeight / zoomFactor; final double orientation = getOrientation(); // todo - use code similar to setZoomFactor(f, vp) (nf - 21.10.2008) final AffineTransform m2v = AffineTransform.getScaleInstance(sx, sy); m2v.scale(zoomFactor, zoomFactor); m2v.translate(-modelOffsetX, -modelOffsetY); modelToViewTransform.setTransform(m2v); updateViewToModelTransform(); this.orientation = 0; // todo - this call (hack?) fires an additional change event! Not good! (nf - 21.10.2008) setOrientation(orientation); fireViewportChanged(false); } void setZoomFactor(double zoomFactor, Point2D vc) { Assert.argument(zoomFactor > 0.0, "zoomFactor > 0.0"); double oldZoomFactor = getZoomFactor(); if (oldZoomFactor != zoomFactor) { AffineTransform v2m = viewToModelTransform; v2m.translate(vc.getX(), vc.getY()); v2m.scale(oldZoomFactor / zoomFactor, oldZoomFactor / zoomFactor); v2m.translate(-vc.getX(), -vc.getY()); updateModelToViewTransform(); fireViewportChanged(false); } } private void setOrientation(double orientation, Point2D vc) { double oldOrientation = getOrientation(); if (oldOrientation != orientation) { final AffineTransform v2m = viewToModelTransform; v2m.translate(vc.getX(), vc.getY()); v2m.rotate(orientation - oldOrientation); v2m.translate(-vc.getX(), -vc.getY()); updateModelToViewTransform(); this.orientation = orientation; fireViewportChanged(true); } } private Point2D.Double getViewportCenterPoint() { return new Point2D.Double(viewBounds.getCenterX(), viewBounds.getCenterY()); } @Override public void addListener(ViewportListener listener) { Assert.notNull(listener, "listener"); if (!changeListeners.contains(listener)) { changeListeners.add(listener); } } @Override public void removeListener(ViewportListener listener) { changeListeners.remove(listener); } @Override public ViewportListener[] getListeners() { return changeListeners.toArray(new ViewportListener[changeListeners.size()]); } @Override public void setTransform(Viewport other) { modelToViewTransform.setTransform(other.getModelToViewTransform()); viewToModelTransform.setTransform(other.getViewToModelTransform()); modelYAxisDown = other.isModelYAxisDown(); final boolean orientationChange = (orientation != other.getOrientation()); if (orientationChange) { orientation = other.getOrientation(); } fireViewportChanged(orientationChange); } protected void fireViewportChanged(final boolean orientationChanged) { for (ViewportListener listener : getListeners()) { listener.handleViewportChanged(this, orientationChanged); } } private void updateModelToViewTransform() { try { modelToViewTransform.setTransform(viewToModelTransform.createInverse()); } catch (NoninvertibleTransformException e) { throw new RuntimeException(e); } } private void updateViewToModelTransform() { try { viewToModelTransform.setTransform(modelToViewTransform.createInverse()); } catch (NoninvertibleTransformException e) { throw new RuntimeException(e); } } @Override public String toString() { return getClass().getName() + "[viewToModelTransform=" + viewToModelTransform + "]"; } @SuppressWarnings({"CloneDoesntDeclareCloneNotSupportedException"}) @Override public Viewport clone() { try { DefaultViewport vp = (DefaultViewport) super.clone(); vp.viewBounds = new Rectangle(viewBounds); vp.changeListeners = new ArrayList<>(3); return vp; } catch (CloneNotSupportedException e) { throw new IllegalStateException(e); } } }