/* * Copyright (c) 2014 tabletoptool.com team. * All rights reserved. This program and the accompanying materials * are made available under the terms of the GNU Public License v3.0 * which accompanies this distribution, and is available at * http://www.gnu.org/licenses/gpl.html * * Contributors: * rptools.com team - initial implementation * tabletoptool.com team - further development */ package com.t3.client.ui; import java.awt.Point; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.io.Serializable; import com.t3.xstreamversioned.version.SerializationVersion; @SerializationVersion(0) public class Scale implements Serializable { private final double oneToOneScale = 1; // Let this be configurable at some point private double scale = oneToOneScale; private final double scaleIncrement = .075; private int zoomLevel = 0; public static final String PROPERTY_SCALE = "scale"; public static final String PROPERTY_OFFSET = "offset"; private transient PropertyChangeSupport propertyChangeSupport; private int offsetX; private int offsetY; private final int width; private final int height; private boolean initialized; public Scale() { this(0, 0); } public Scale(int width, int height) { this.width = width; this.height = height; } public Scale(Scale copy) { this.width = copy.width; this.height = copy.height; this.offsetX = copy.offsetX; this.offsetY = copy.offsetY; this.zoomLevel = copy.zoomLevel; this.initialized = copy.initialized; this.scale = copy.scale; // this.oneToOneScale = copy.oneToOneScale; // this.scaleIncrement = copy.scaleIncrement; } public void addPropertyChangeListener(PropertyChangeListener listener) { getPropertyChangeSupport().addPropertyChangeListener(listener); } public void addPropertyChangeListener(String property, PropertyChangeListener listener) { getPropertyChangeSupport().addPropertyChangeListener(property, listener); } public void removePropertyChangeListener(PropertyChangeListener listener) { getPropertyChangeSupport().removePropertyChangeListener(listener); } public void removePropertyChangeListener(String property, PropertyChangeListener listener) { getPropertyChangeSupport().removePropertyChangeListener(property, listener); } public int getOffsetX() { return offsetX; } public int getOffsetY() { return offsetY; } public void setOffset(int x, int y) { int oldX = offsetX; int oldY = offsetY; offsetX = x; offsetY = y; getPropertyChangeSupport().firePropertyChange(PROPERTY_OFFSET, new Point(oldX, oldY), new Point(offsetX, offsetY)); } public double getScale() { return scale; } public void setScale(double scale) { if (scale <= 0.0) { return; } // Determine zoomLevel appropriate for given scale zoomLevel = (int)Math.round(Math.log(scale/oneToOneScale)/Math.log(1+scaleIncrement)); setScaleNoZoomLevel(scale); } private void setScaleNoZoomLevel(double scale) { double oldScale = this.scale; this.scale = scale; getPropertyChangeSupport().firePropertyChange(PROPERTY_SCALE, oldScale, scale); } public double getOneToOneScale() { return oneToOneScale; } public double reset() { double oldScale = this.scale; scale = oneToOneScale; zoomLevel = 0; getPropertyChangeSupport().firePropertyChange(PROPERTY_SCALE, oldScale, scale); return oldScale; } public double scaleUp() { zoomLevel++; setScaleNoZoomLevel(oneToOneScale * Math.pow(1+scaleIncrement, zoomLevel)); return scale; } public double scaleDown() { zoomLevel--; setScaleNoZoomLevel(oneToOneScale * Math.pow(1+scaleIncrement, zoomLevel)); return scale; } public void zoomReset(int x, int y) { zoomTo(x, y, reset()); } public void zoomIn(int x, int y) { double oldScale = scale; scaleUp(); zoomTo(x, y, oldScale); } public void zoomOut(int x, int y) { double oldScale = scale; scaleDown(); zoomTo(x, y, oldScale); } public void zoomScale(int x, int y, double scale) { double oldScale = this.scale; setScale(scale); zoomTo(x, y, oldScale); } public boolean isInitialized() { return initialized; } private PropertyChangeSupport getPropertyChangeSupport() { if (propertyChangeSupport == null) { propertyChangeSupport = new PropertyChangeSupport(this); } return propertyChangeSupport; } /** * Fit the image into the given space by finding the zoom level * that allows the image to fit. Then center the image. * @param width * @param height * @return true if this call did something, false if the init has already been called */ public boolean initialize(int width, int height) { if (initialized) { return false; } centerIn(width, height); initialized = true; return true; } public void centerIn(int width, int height) { int currWidth = (int)(this.width * getScale()); int currHeight = (int)(this.height * getScale()); int x = (width - currWidth) / 2; int y = (height - currHeight) / 2; setOffset(x, y); } private void zoomTo(int x, int y, double oldScale) { // Keep the current pixel centered x -= offsetX; y -= offsetY; // Rounding reduces drift in offset from repeated zooming int newX = (int) Math.round((x * scale) / oldScale); int newY = (int) Math.round((y * scale) / oldScale); offsetX = offsetX-(newX - x); offsetY = offsetY-(newY - y); } }