/*******************************************************************************
* Copyright (c) 2009 the CHISEL group and contributors.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Del Myers -- initial API and implementation
*******************************************************************************/
package org.eclipse.zest.custom.sequence.figures.internal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import org.eclipse.draw2d.FreeformFigure;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.ScalableFigure;
import org.eclipse.draw2d.ScalableFreeformLayeredPane;
import org.eclipse.draw2d.Viewport;
import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.swt.widgets.Display;
/**
* Manage the primary zoom function in a graphical viewer. This class is used by
* the zoom contribution items, including:
* <UL>
* <LI>{@link org.eclipse.gef.ui.actions.ZoomInAction}
* <LI>{@link org.eclipse.gef.ui.actions.ZoomOutAction}
* <LI> and {@link org.eclipse.gef.ui.actions.ZoomComboContributionItem}
* </UL>
* <P>
* A ZoomManager controls how zoom in and zoom out are performed. It also
* determines the list of choices the user sees in the drop-down Combo on the
* toolbar. The zoom manager controls a <code>ScalableFigure</code>, which
* performs the actual zoom, and also a <code>Viewport</code>. The viewport
* is needed so that the scrolled location is preserved as the zoom level
* changes.
* <p>
* <b>NOTE:</b> For the settings of {@link #FIT_ALL Page},
* {@link #FIT_WIDTH Width} and {@link #FIT_HEIGHT Height} to work properly, the
* given <code>Viewport</code> should have its scrollbars always visible or
* never visible. Otherwise, these settings may cause undesired effects.
*
* @author Dan Lee
* @author Eric Bordeau
*/
@SuppressWarnings("unchecked")
public class ZoomManager {
/** Style bit meaning don't animate any zooms */
public static final int ANIMATE_NEVER = 0;
/** Style bit meaning animate during {@link #zoomIn()} and {@link #zoomOut()} */
public static final int ANIMATE_ZOOM_IN_OUT = 1;
private List listeners = new ArrayList();
private double multiplier = 1.0;
private ScalableFigure pane;
private Viewport viewport;
private double zoom = 1.0;
//private int zoomAnimationStyle = ANIMATE_NEVER;
private String currentZoomContant = null;
private double[] zoomLevels = { .5, .75, 1.0, 1.5, 2.0, 2.5, 3, 4 };
/**
* String constant for the "Height" zoom level. At this zoom level, the zoom
* manager will adopt a zoom setting such that the entire height of the
* diagram will be visible on the screen.
*/
public static final String FIT_HEIGHT = SharedMessages.FitHeightAction_Label;
/**
* String constant for the "Width" zoom level. At this zoom level, the zoom
* manager will adopt a zoom setting such that the entire width of the
* diagram will be visible on the screen.
*/
public static final String FIT_WIDTH = SharedMessages.FitWidthAction_Label;
/**
* String constant for the "Page" zoom level. At this zoom level, the zoom
* manager will adopt a zoom setting such that the entire diagram will be
* visible on the screen.
*/
public static final String FIT_ALL = SharedMessages.FitAllAction_Label;
private List zoomLevelContributions = Collections.EMPTY_LIST;
//DecimalFormat format = new DecimalFormat("####%"); //$NON-NLS-1$
/**
* Creates a new ZoomManager.
*
* @param pane
* The ScalableFigure associated with this ZoomManager
* @param viewport
* The Viewport assoicated with this ZoomManager
*/
public ZoomManager(ScalableFigure pane, Viewport viewport) {
this.pane = pane;
this.viewport = viewport;
zoomLevelContributions = new ArrayList();
zoomLevelContributions.add(FIT_ALL);
}
/**
* @deprecated Use {@link #ZoomManager(ScalableFigure, Viewport)} instead.
* Creates a new ZoomManager
* @param pane
* The ScalableFreeformLayeredPane associated with this
* ZoomManager
* @param viewport
* The Viewport assoicated with this viewport
*/
public ZoomManager(ScalableFreeformLayeredPane pane, Viewport viewport) {
this.pane = pane;
this.viewport = viewport;
}
/**
* Adds the given ZoomListener to this ZoomManager's list of listeners.
*
* @param listener
* the ZoomListener to be added
*/
public void addZoomListener(ZoomListener listener) {
listeners.add(listener);
}
/**
* returns <code>true</code> if the zoommanager can perform
* <code>zoomIn()</code>.
*
* @return boolean true if zoomIn can be called
*/
public boolean canZoomIn() {
return getZoom() < getMaxZoom();
}
/**
* returns <code>true</code> if the zoommanager can perform
* <code>zoomOut()</code>.
*
* @return boolean true if zoomOut can be called
*/
public boolean canZoomOut() {
return getZoom() > getMinZoom();
}
/**
* Notifies listeners that the zoom level has changed.
*/
protected void fireZoomChanged() {
Iterator iter = listeners.iterator();
while (iter.hasNext()) {
((ZoomListener) iter.next()).zoomChanged(zoom);
}
}
private double getFitXZoomLevel(int which) {
IFigure fig = getScalableFigure();
Dimension available = getViewport().getClientArea().getSize();
Dimension desired;
if (fig instanceof FreeformFigure) {
desired = ((FreeformFigure) fig).getFreeformExtent().getCopy().union(0, 0).getSize();
} else {
desired = fig.getPreferredSize().getCopy();
}
desired.width -= fig.getInsets().getWidth();
desired.height -= fig.getInsets().getHeight();
while (fig != getViewport()) {
available.width -= fig.getInsets().getWidth();
available.height -= fig.getInsets().getHeight();
fig = fig.getParent();
}
double scaleX = Math.min(available.width * zoom / desired.width, getMaxZoom());
double scaleY = Math.min(available.height * zoom / desired.height, getMaxZoom());
if (which == 0) {
return scaleX;
}
if (which == 1) {
return scaleY;
}
return Math.min(scaleX, scaleY);
}
/**
* Calculates and returns the zoom percent required so that the entire
* height of the {@link #getScalableFigure() scalable figure} is visible on
* the screen. This is the zoom level associated with {@link #FIT_HEIGHT}.
*
* @return zoom setting required to fit the scalable figure vertically on
* the screen
*/
protected double getFitHeightZoomLevel() {
return getFitXZoomLevel(1);
}
/**
* Calculates and returns the zoom percentage required to fit the entire
* {@link #getScalableFigure() scalable figure} on the screen. This is the
* zoom setting associated with {@link #FIT_ALL}. It is the minimum of
* {@link #getFitHeightZoomLevel()} and {@link #getFitWidthZoomLevel()}.
*
* @return zoom setting required to fit the entire scalable figure on the
* screen
*/
protected double getFitPageZoomLevel() {
return getFitXZoomLevel(2);
}
/**
* Calculates and returns the zoom percentage required so that the entire
* width of the {@link #getScalableFigure() scalable figure} is visible on
* the screen. This is the zoom setting associated with {@link #FIT_WIDTH}.
*
* @return zoom setting required to fit the scalable figure horizontally on
* the screen
*/
protected double getFitWidthZoomLevel() {
return getFitXZoomLevel(0);
}
/**
* Returns the maxZoom.
*
* @return double
*/
public double getMaxZoom() {
return getZoomLevels()[getZoomLevels().length - 1];
}
/**
* Returns the minZoom.
*
* @return double
*/
public double getMinZoom() {
return getZoomLevels()[0];
}
/**
* Returns the mutltiplier. This value is used to use zoom levels internally
* that are proportionally different than those displayed to the user. e.g.
* with a multiplier value of 2.0, the zoom level 1.0 will be displayed as
* "200%".
*
* @return double The multiplier
*/
public double getUIMultiplier() {
return multiplier;
}
/**
* Returns the zoom level that is one level higher than the current level.
* If zoom level is at maximum, returns the maximum.
*
* @return double The next zoom level
*/
public double getNextZoomLevel() {
for (int i = 0; i < zoomLevels.length; i++) {
if (zoomLevels[i] > zoom) {
return zoomLevels[i];
}
}
return getMaxZoom();
}
/**
* Returns the zoom level that is one level higher than the current level.
* If zoom level is at maximum, returns the maximum.
*
* @return double The previous zoom level
*/
public double getPreviousZoomLevel() {
for (int i = 1; i < zoomLevels.length; i++) {
if (zoomLevels[i] >= zoom) {
return zoomLevels[i - 1];
}
}
return getMinZoom();
}
/**
* Returns the figure which performs the actual zooming.
*
* @return the scalable figure
*/
public ScalableFigure getScalableFigure() {
return pane;
}
/**
* Returns the viewport.
*
* @return Viewport
*/
public Viewport getViewport() {
return viewport;
}
/**
* Returns the current zoom level.
*
* @return double the zoom level
*/
public double getZoom() {
return zoom;
}
private String format(double d) {
return "" + ((int) (d * 100)) + "%";
}
/**
* Returns the current zoom level as a percentage formatted String
*
* @return String The current zoom level as a String
*/
public String getZoomAsText() {
if (currentZoomContant != null) {
return currentZoomContant;
}
//String newItem = format.format(zoom * multiplier);
String newItem = format(zoom * multiplier);
return newItem;
}
/**
* Returns the list of strings that should be appended to the list of
* numerical zoom levels. These could be things such as Fit Width, Fit Page,
* etc. May return <code>null</code>.
*
* @return the list of contributed zoom levels
*/
public List getZoomLevelContributions() {
return zoomLevelContributions;
}
/**
* Returns the zoomLevels.
*
* @return double[]
*/
public double[] getZoomLevels() {
return zoomLevels;
}
/**
* Returns the list of zoom levels as Strings in percent notation, plus any
* additional zoom levels that were contributed using
* {@link #setZoomLevelContributions(List)}.
*
* @return List The list of zoom levels
*/
public String[] getZoomLevelsAsText() {
String[] zoomLevelStrings = new String[zoomLevels.length + zoomLevelContributions.size()];
if (zoomLevelContributions != null) {
for (int i = 0; i < zoomLevelContributions.size(); i++) {
zoomLevelStrings[i] = (String) zoomLevelContributions.get(i);
}
}
for (int i = 0; i < zoomLevels.length; i++) {
//zoomLevelStrings[i + zoomLevelContributions.size()] = format.format(zoomLevels[i] * multiplier);
zoomLevelStrings[i + zoomLevelContributions.size()] = format(zoomLevels[i] * multiplier);
}
return zoomLevelStrings;
}
/**
* Sets the zoom level to the given value. Min-max range check is not done.
*
* @param zoom
* the new zoom level
*/
protected void primSetZoom(double zoom) {
Point p1 = getViewport().getClientArea().getCenter();
Point p2 = p1.getCopy();
Point p = getViewport().getViewLocation();
double prevZoom = this.zoom;
this.zoom = zoom;
pane.setScale(zoom);
fireZoomChanged();
getViewport().validate();
p2.scale(zoom / prevZoom);
Dimension dif = p2.getDifference(p1);
p.x += dif.width;
p.y += dif.height;
setViewLocation(p);
}
/**
* Removes the given ZoomListener from this ZoomManager's list of listeners.
*
* @param listener
* the ZoomListener to be removed
*/
public void removeZoomListener(ZoomListener listener) {
listeners.remove(listener);
}
/**
* Sets the UI multiplier. The UI multiplier is applied to all zoom settings
* when they are presented to the user ({@link #getZoomAsText()}).
* Similarly, the multiplier is inversely applied when the user specifies a
* zoom level ({@link #setZoomAsText(String)}).
* <P>
* When the UI multiplier is <code>1.0</code>, the User will see the
* exact zoom level that is being applied. If the value is <code>2.0</code>,
* then a scale of <code>0.5</code> will be labeled "100%" to the User.
*
* @param multiplier
* The mutltiplier to set
*/
public void setUIMultiplier(double multiplier) {
this.multiplier = multiplier;
}
/**
* Sets the Viewport's view associated with this ZoomManager to the passed
* Point
*
* @param p
* The new location for the Viewport's view.
*/
public void setViewLocation(Point p) {
viewport.setViewLocation(p.x, p.y);
}
/**
* Sets the zoom level to the given value. If the zoom is out of the min-max
* range, it will be ignored.
*
* @param zoom
* the new zoom level
*/
public void setZoom(double zoom) {
currentZoomContant = null;
zoom = Math.min(getMaxZoom(), zoom);
zoom = Math.max(getMinZoom(), zoom);
if (this.zoom != zoom) {
primSetZoom(zoom);
}
}
/**
* Sets which zoom methods get animated.
*
* @param style
* the style bits determining the zoom methods to be animated.
*/
public void setZoomAnimationStyle(int style) {
//zoomAnimationStyle = style;
}
/**
* Sets zoom to the passed string. The string must be composed of numeric
* characters only with the exception of a decimal point and a '%' as the
* last character. If the zoom level contribution list has been set, this
* method should be overridden to provide the appropriate zoom
* implementation for the new zoom levels.
*
* @param zoomString
* The new zoom level
*/
public void setZoomAsText(String zoomString) {
currentZoomContant = null;
if (zoomString.equalsIgnoreCase(FIT_HEIGHT)) {
currentZoomContant = FIT_HEIGHT;
primSetZoom(getFitHeightZoomLevel());
viewport.getUpdateManager().performUpdate();
viewport.setViewLocation(viewport.getHorizontalRangeModel().getValue(), viewport.getVerticalRangeModel().getMinimum());
} else if (zoomString.equalsIgnoreCase(FIT_ALL)) {
currentZoomContant = FIT_ALL;
primSetZoom(getFitPageZoomLevel());
viewport.getUpdateManager().performUpdate();
viewport.setViewLocation(viewport.getHorizontalRangeModel().getMinimum(), viewport.getVerticalRangeModel().getMinimum());
} else if (zoomString.equalsIgnoreCase(FIT_WIDTH)) {
currentZoomContant = FIT_WIDTH;
primSetZoom(getFitWidthZoomLevel());
viewport.getUpdateManager().performUpdate();
viewport.setViewLocation(viewport.getHorizontalRangeModel().getMinimum(), viewport.getVerticalRangeModel().getValue());
} else {
try {
//Trim off the '%'
if (zoomString.charAt(zoomString.length() - 1) == '%') {
zoomString = zoomString.substring(0, zoomString.length() - 1);
}
double newZoom = Double.parseDouble(zoomString) / 100;
setZoom(newZoom / multiplier);
} catch (Exception e) {
Display.getCurrent().beep();
}
}
}
/**
* Sets the list of zoom level contributions (as strings). If you contribute
* something <b>other than</b> {@link #FIT_HEIGHT}, {@link #FIT_WIDTH} and
* {@link #FIT_ALL} you must subclass this class and override this method to
* implement your contributed zoom function.
*
* @param contributions
* the list of contributed zoom levels
*/
public void setZoomLevelContributions(List contributions) {
zoomLevelContributions = contributions;
}
/**
* Sets the zoomLevels.
*
* @param zoomLevels
* The zoomLevels to set
*/
public void setZoomLevels(double[] zoomLevels) {
this.zoomLevels = zoomLevels;
}
/**
* Sets the zoom level to be one level higher
*/
public void zoomIn() {
setZoom(getNextZoomLevel());
}
/**
* Currently does nothing.
*
* @param rect
* a rectangle
*/
public void zoomTo(Rectangle rect) {
}
//private void performAnimatedZoom(Rectangle rect, boolean zoomIn, int iterationCount) {
// double finalRatio;
// double zoomIncrement;
//
// if (zoomIn) {
// finalRatio = zoom / getNextZoomLevel();
// zoomIncrement = (getNextZoomLevel() - zoom) / iterationCount;
// } else {
// finalRatio = zoom / getPreviousZoomLevel();
// zoomIncrement = (getPreviousZoomLevel() - zoom) / iterationCount;
// }
//
// getScalableFigure().translateToRelative(rect);
// Point originalViewLocation = getViewport().getViewLocation();
// Point finalViewLocation = calculateViewLocation(rect, finalRatio);
//
// double xIncrement =
// (double) (finalViewLocation.x - originalViewLocation.x) / iterationCount;
// double yIncrement =
// (double) (finalViewLocation.y - originalViewLocation.y) / iterationCount;
//
// double originalZoom = zoom;
// Point currentViewLocation = new Point();
// for (int i = 1; i < iterationCount; i++) {
// currentViewLocation.x = (int)(originalViewLocation.x + (xIncrement * i));
// currentViewLocation.y = (int)(originalViewLocation.y + (yIncrement * i));
// setZoom(originalZoom + zoomIncrement * i);
// getViewport().validate();
// setViewLocation(currentViewLocation);
// getViewport().getUpdateManager().performUpdate();
// }
//
// if (zoomIn)
// setZoom(getNextZoomLevel());
// else
// setZoom(getPreviousZoomLevel());
//
// getViewport().validate();
// setViewLocation(finalViewLocation);
//}
//
//private Point calculateViewLocation(Rectangle zoomRect, double ratio) {
// Point viewLocation = new Point();
// viewLocation.x = (int)(zoomRect.x / ratio);
// viewLocation.y = (int)(zoomRect.y / ratio);
// return viewLocation;
//}
/**
* Sets the zoom level to be one level lower
*/
public void zoomOut() {
setZoom(getPreviousZoomLevel());
}
}