/* ****************************************************************************** * Copyright (c) 2006-2012 XMind Ltd. and others. * * This file is a part of XMind 3. XMind releases 3 and * above are dual-licensed under the Eclipse Public License (EPL), * which is available at http://www.eclipse.org/legal/epl-v10.html * and the GNU Lesser General Public License (LGPL), * which is available at http://www.gnu.org/licenses/lgpl.html * See http://www.xmind.net/license.html for details. * * Contributors: * XMind Ltd. - initial API and implementation *******************************************************************************/ package org.xmind.gef.service; import java.util.ArrayList; import java.util.List; import org.eclipse.draw2d.geometry.Rectangle; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.xmind.gef.IGraphicalViewer; import org.xmind.gef.draw2d.geometry.Geometry; import org.xmind.gef.draw2d.geometry.PrecisionPoint; import org.xmind.gef.part.IGraphicalPart; public abstract class ZoomingAndPanningRevealService extends BaseRevealService { private class RevealJob implements Runnable { private Display display; private List<IGraphicalPart> toReveal; private long startTime = -1; private boolean canceled = false; private int elapsedSteps = -1; public RevealJob(Display display, List<IGraphicalPart> toReveal) { this.display = display; this.toReveal = toReveal; } public void cancel() { boolean oldCanceled = this.canceled; setCanceled(); if (!oldCanceled) { revealingCanceled(new RevealEvent( ZoomingAndPanningRevealService.this, toReveal)); } } private void setCanceled() { canceled = true; } public void run() { if (canceled) return; Control control = getViewer().getControl(); if (control == null || control.isDisposed()) { setCanceled(); return; } if (!isAnimationEnabled()) { finish(); return; } long currentTime = System.currentTimeMillis(); if (startTime < 0) startTime = currentTime; int elapsedTime = (int) (currentTime - startTime); int remainingTime = getDuration() - elapsedTime; if (remainingTime <= 0) { finish(); return; } double intervals = INTERVALS; if (elapsedSteps < 0) { elapsedSteps = 0; intervals += 10; } else { intervals += (((double) (currentTime - startTime - INTERVALS * elapsedSteps)) / elapsedSteps); } int remainingSteps = (int) ((remainingTime + intervals - 1) / intervals); if (remainingSteps <= 0) { finish(); return; } Rectangle revealBounds = getRevealBounds(toReveal); if (revealBounds == null) { cancel(); return; } doStep(toReveal, revealBounds, remainingSteps); display.timerExec(INTERVALS, this); elapsedSteps++; } public void finish() { setCanceled(); revealJobFinished(toReveal); } } private static final int INTERVALS = 20; private int duration = 200; private int delay = 100; private RevealJob job = null; private double cachedScale = -1; private boolean centered = false; private int spacing = 20; private boolean zoomed = false; private boolean animationEnabled = true; private boolean shouldRevealOnIntersection = true; protected ZoomingAndPanningRevealService(IGraphicalViewer viewer) { super(viewer); } public int getDuration() { return duration; } public void setDuration(int duration) { this.duration = duration; } public int getDelay() { return delay; } public void setDelay(int delay) { this.delay = delay; } public void setCentered(boolean centered) { this.centered = centered; } public boolean isCentered() { return this.centered; } public void setSpacing(int spacing) { this.spacing = spacing; } public int getSpacing() { return this.spacing; } /** * @param zoomed * the zoomed to set */ public void setZoomed(boolean zoomed) { this.zoomed = zoomed; } public void setShouldRevealOnIntersection(boolean should) { this.shouldRevealOnIntersection = should; } public boolean isShouldRevealOnIntersection() { return shouldRevealOnIntersection; } /** * @return the zoomed */ public boolean isZoomed() { return this.zoomed; } public void setAnimationEnabled(boolean animationEnabled) { this.animationEnabled = animationEnabled; } protected boolean isAnimationEnabled() { return animationEnabled; } protected void activate() { } protected void deactivate() { } public void reveal(ISelection selection) { if (!isActive()) return; startReveal(selection); } protected void startReveal(ISelection selection) { if (job != null) { job.cancel(); job = null; } cachedScale = -1; List<IGraphicalPart> toReveal = collectPartsToReveal(selection); if (toReveal != null && shouldReveal(toReveal)) { Display display = Display.getCurrent(); job = new RevealJob(display, toReveal); display.timerExec(delay, job); revealingStarted(new RevealEvent(this, toReveal)); } } protected boolean shouldReveal(List<IGraphicalPart> toReveal) { return !toReveal.isEmpty(); } protected double calcTargetScale(List<IGraphicalPart> toReveal, Rectangle revealBounds) { if (!isZoomed()) return -1; if (cachedScale > 0) return cachedScale; Rectangle clientArea = getViewer().getClientArea(); int width = revealBounds.width; int height = revealBounds.height; double scale = 2.3d; double w = width * scale; double h = height * scale; double minWidth = clientArea.width * 0.08d; double minHeight = clientArea.height * 0.08d; if (w < minWidth || h < minHeight) { double s1 = w < minWidth ? minWidth / width : scale; double s2 = h < minHeight ? minHeight / height : scale; scale = Math.max(s1, s2); w = width * scale; h = height * scale; } double maxWidth = clientArea.width * 0.6d; double maxHeight = clientArea.height * 0.6d; if (w > maxWidth || h > maxHeight) { double s1 = w > maxWidth ? maxWidth / width : scale; double s2 = h > maxHeight ? maxHeight / height : scale; scale = Math.min(s1, s2); } cachedScale = scale; return scale; } protected PrecisionPoint calcTargetCenter(List<IGraphicalPart> toReveal, Rectangle revealBounds, double targetScale) { if (isCentered()) { return new PrecisionPoint(revealBounds.getCenter()); } else { return calcLeastTargetCenter(toReveal, revealBounds, targetScale); } } /** * @param toReveal * @param revealBounds * @param targetScale * @return */ protected PrecisionPoint calcLeastTargetCenter( List<IGraphicalPart> toReveal, Rectangle revealBounds, double targetScale) { Rectangle clientArea = getViewerClientArea(); revealBounds.expand(getSpacing(), getSpacing()); if (shouldReveal(revealBounds, clientArea)) { int dx = 0; int dy = 0; int margin = 50; int offsetH = clientArea.getBottom().y - clientArea.getCenter().y; int offsetV = clientArea.getRight().x - clientArea.getCenter().x; if (revealBounds.width > clientArea.width) dx = revealBounds.x - clientArea.getCenter().x; else if (revealBounds.x < clientArea.x) dx = revealBounds.x + offsetV - margin - getSpacing(); else if (revealBounds.right() > clientArea.right()) dx = revealBounds.right() - offsetV + margin + getSpacing(); if (revealBounds.height > clientArea.height) dy = revealBounds.y - clientArea.getCenter().y; else if (revealBounds.y < clientArea.y) dy = revealBounds.y + offsetH - margin - getSpacing(); else if (revealBounds.bottom() > clientArea.bottom()) dy = revealBounds.bottom() - offsetH + margin + getSpacing(); return getViewerCenterPoint(getViewerScale()).translate(dx, dy); } return null; } protected boolean shouldReveal(Rectangle revealBounds, Rectangle clientArea) { if (isShouldRevealOnIntersection()) { return !clientArea.contains(revealBounds) && !revealBounds.contains(clientArea); } return clientArea.bottom() < revealBounds.bottom() || clientArea.getTop().y > revealBounds.y; } protected Rectangle getViewerClientArea() { Rectangle clientArea = getViewer().getClientArea(); return getViewer().getZoomManager().getAntiScaled(clientArea); } protected Rectangle getRevealBounds(List<IGraphicalPart> parts) { Rectangle r = null; for (IGraphicalPart p : parts) { r = Geometry.union(r, getRevealBounds(p)); } return r; } protected Rectangle getRevealBounds(IGraphicalPart p) { return p.getFigure().getBounds(); } protected double getViewerScale() { return getViewer().getZoomManager().getScale(); } protected PrecisionPoint getViewerCenterPoint(double scale) { return new PrecisionPoint(getViewer().getCenterPoint()) .scale(1 / scale); } protected List<IGraphicalPart> collectPartsToReveal(ISelection selection) { if (selection instanceof IStructuredSelection) { IStructuredSelection ss = (IStructuredSelection) selection; List<IGraphicalPart> list = new ArrayList<IGraphicalPart>( ss.size()); for (Object o : ss.toList()) { IGraphicalPart p = getViewer().findGraphicalPart(o); if (p != null && !exclude(p)) { list.add(p); } } return list; } return null; } protected boolean exclude(IGraphicalPart part) { return false; } protected void finishCurrentJob() { if (job != null) { job.finish(); job = null; } } protected void cancelCurrentJob() { if (job != null) { job.cancel(); job = null; } } protected void revealJobFinished(List<IGraphicalPart> toReveal) { // Rectangle revealBounds = getRevealBounds(toReveal); // if (revealBounds != null) { // double targetScale = calcTargetScale(toReveal, revealBounds); // PrecisionPoint targetCenter = calcTargetCenter(toReveal, // revealBounds, targetScale); // if (targetScale > 0) { // getViewer().getZoomManager().setScale(targetScale); // } // if (targetCenter != null) { // getViewer().center(targetCenter.getScaled(getViewerScale()) // .toRoundedDraw2DPoint()); // } // } revealingFinished(new RevealEvent(this, toReveal)); } protected void doStep(List<IGraphicalPart> toReveal, Rectangle revealBounds, int remainingSteps) { double scale = getViewerScale(); PrecisionPoint center = getViewerCenterPoint(scale); double targetScale = calcTargetScale(toReveal, revealBounds); PrecisionPoint targetCenter = calcTargetCenter(toReveal, revealBounds, targetScale); if (targetScale > 0) { double remainingScale = targetScale - scale; double stepScale = remainingScale / remainingSteps; scale += stepScale; getViewer().getZoomManager().setScale(scale); } if (targetCenter != null) { double horizontalOffset = targetCenter.x - center.x; double verticalOffset = targetCenter.y - center.y; double stepX = horizontalOffset / remainingSteps; double stepY = verticalOffset / remainingSteps; center.x += stepX; center.y += stepY; getViewer().center(targetCenter.getScaled(getViewerScale()) .toRoundedDraw2DPoint()); } } }