/******************************************************************************* * Copyright (c) 2000, 2010 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.draw2d; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.eclipse.swt.SWT; import org.eclipse.swt.SWTException; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.widgets.Display; import org.eclipse.draw2d.geometry.Rectangle; /** * An UpdateManager that asynchronously updates the affected figures. */ public class DeferredUpdateManager extends UpdateManager { /** * Calls {@link DeferredUpdateManager#performUpdate()}. */ protected class UpdateRequest implements Runnable { public UpdateRequest() { super(); } /** * Calls {@link DeferredUpdateManager#performUpdate()}. */ public void run() { performUpdate(); } } private Rectangle damage; private Map dirtyRegions = new HashMap(); private GraphicsSource graphicsSource; private List invalidFigures = new ArrayList(); private IFigure root; private boolean updateQueued; private boolean updating; private boolean validating; private RunnableChain afterUpdate; private static class RunnableChain { RunnableChain next; Runnable run; RunnableChain(Runnable run, RunnableChain next) { this.run = run; this.next = next; } void run() { if (next != null) next.run(); run.run(); } } /** * Empty constructor. */ public DeferredUpdateManager() { } /** * Constructs a new DererredUpdateManager with the given GraphicsSource. * * @param gs * the graphics source */ public DeferredUpdateManager(GraphicsSource gs) { setGraphicsSource(gs); } /** * Adds a dirty region (defined by the rectangle <i>x, y, w, h</i>) to the * update queue. If the figure isn't visible or either the width or height * are 0, the method returns without queueing the dirty region. * * @param figure * the figure that contains the dirty region * @param x * the x coordinate of the dirty region * @param y * the y coordinate of the dirty region * @param w * the width of the dirty region * @param h * the height of the dirty region */ public synchronized void addDirtyRegion(IFigure figure, int x, int y, int w, int h) { if (w == 0 || h == 0 || !figure.isShowing()) return; Rectangle rect = (Rectangle) dirtyRegions.get(figure); if (rect == null) { rect = new Rectangle(x, y, w, h); dirtyRegions.put(figure, rect); } else rect.union(x, y, w, h); queueWork(); } /** * Adds the given figure to the update queue. Invalid figures will be * validated before the damaged regions are repainted. * * @param f * the invalid figure */ public synchronized void addInvalidFigure(IFigure f) { if (invalidFigures.contains(f)) return; queueWork(); invalidFigures.add(f); } /** * Returns a Graphics object for the given region. * * @param region * the region to be repainted * @return the Graphics object */ protected Graphics getGraphics(Rectangle region) { if (graphicsSource == null) return null; return graphicsSource.getGraphics(region); } /** * @since 3.10 */ protected void paint(GC gc) { if (!validating) { SWTGraphics graphics = new SWTGraphics(gc); if (!updating) { /** * If a paint occurs not as part of an update, we should notify * that the region is being painted. Otherwise, notification * already occurs in repairDamage(). */ Rectangle rect = graphics.getClip(new Rectangle()); HashMap map = new HashMap(); map.put(root, rect); firePainting(rect, map); } performValidation(); root.paint(graphics); graphics.dispose(); } else { /* * If figures are being validated then we can simply add a dirty * region here and update will repaint this region with other dirty * regions when it gets to painting. We can't paint if we're not * sure that all figures are valid. */ addDirtyRegion(root, new Rectangle(gc.getClipping())); } } /** * Performs the update. Validates the invalid figures and then repaints the * dirty regions. * * @see #validateFigures() * @see #repairDamage() */ public synchronized void performUpdate() { if (isDisposed() || updating) return; updating = true; try { performValidation(); updateQueued = false; repairDamage(); if (afterUpdate != null) { RunnableChain chain = afterUpdate; afterUpdate = null; chain.run(); // chain may queue additional Runnable. if (afterUpdate != null) queueWork(); } } finally { updating = false; } } /** * @see UpdateManager#performValidation() */ public synchronized void performValidation() { if (invalidFigures.isEmpty() || validating) return; try { IFigure fig; validating = true; fireValidating(); for (int i = 0; i < invalidFigures.size(); i++) { fig = (IFigure) invalidFigures.get(i); invalidFigures.set(i, null); fig.validate(); } } finally { invalidFigures.clear(); validating = false; } } /** * Adds the given exposed region to the update queue and then performs the * update. * * @param exposed * the exposed region */ public synchronized void performUpdate(Rectangle exposed) { addDirtyRegion(root, exposed); performUpdate(); } /** * Posts an {@link UpdateRequest} using {@link Display#asyncExec(Runnable)}. * If work has already been queued, a new request is not needed. */ protected void queueWork() { if (!updateQueued) { sendUpdateRequest(); updateQueued = true; } } /** * Fires the <code>UpdateRequest</code> to the current display * asynchronously. * * @since 3.2 */ protected void sendUpdateRequest() { Display display = Display.getCurrent(); if (display == null) { throw new SWTException(SWT.ERROR_THREAD_INVALID_ACCESS); } display.asyncExec(new UpdateRequest()); } /** * Releases the graphics object, which causes the GraphicsSource to flush. * * @param graphics * the graphics object */ protected void releaseGraphics(Graphics graphics) { graphics.dispose(); graphicsSource.flushGraphics(damage); } /** * Repaints the dirty regions on the update queue and calls * {@link UpdateManager#firePainting(Rectangle, Map)}, unless there are no * dirty regions. */ protected void repairDamage() { Iterator keys = dirtyRegions.keySet().iterator(); Rectangle contribution; IFigure figure; IFigure walker; while (keys.hasNext()) { figure = (IFigure) keys.next(); walker = figure.getParent(); contribution = (Rectangle) dirtyRegions.get(figure); // A figure can't paint beyond its own bounds contribution.intersect(figure.getBounds()); while (!contribution.isEmpty() && walker != null) { walker.translateToParent(contribution); contribution.intersect(walker.getBounds()); walker = walker.getParent(); } if (damage == null) damage = new Rectangle(contribution); else damage.union(contribution); } if (!dirtyRegions.isEmpty()) { Map oldRegions = dirtyRegions; dirtyRegions = new HashMap(); firePainting(damage, oldRegions); } if (damage != null && !damage.isEmpty()) { // ystem.out.println(damage); Graphics graphics = getGraphics(damage); if (graphics != null) { root.paint(graphics); releaseGraphics(graphics); } } damage = null; } /** * Adds the given runnable and queues an update if an update is not under * progress. * * @param runnable * the runnable */ public synchronized void runWithUpdate(Runnable runnable) { afterUpdate = new RunnableChain(runnable, afterUpdate); if (!updating) queueWork(); } /** * Sets the graphics source. * * @param gs * the graphics source */ public void setGraphicsSource(GraphicsSource gs) { graphicsSource = gs; } /** * Sets the root figure. * * @param figure * the root figure */ public void setRoot(IFigure figure) { root = figure; } /** * Validates all invalid figures on the update queue and calls * {@link UpdateManager#fireValidating()} unless there are no invalid * figures. */ protected void validateFigures() { performValidation(); } }