/******************************************************************************* * Copyright (c) 2000, 2005 IBM Corporation and others. 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: IBM Corporation - initial API and implementation ******************************************************************************/ package org.eclipse.zest.core.viewers.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 */ 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()); } }