/******************************************************************************* * 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 * SAP AG - Changed for initial API, implementation and documentation of Graphiti: * Used ScaledGraphics instead of Graphics for thumbnail * Changed method run to handle the complete rectangle instead of tiles * Changed resetThumbnailImage to dispose if not empty ******************************************************************************/ package org.eclipse.graphiti.ui.internal.fixed; import java.util.Iterator; import java.util.Map; import org.eclipse.draw2d.Figure; import org.eclipse.draw2d.Graphics; import org.eclipse.draw2d.IFigure; import org.eclipse.draw2d.SWTGraphics; import org.eclipse.draw2d.UpdateListener; import org.eclipse.draw2d.geometry.Dimension; import org.eclipse.draw2d.geometry.Rectangle; import org.eclipse.swt.SWT; import org.eclipse.swt.SWTException; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.widgets.Display; /** * The Class FixedThumbnail. * * @noinstantiate This class is not intended to be instantiated by clients. * @noextend This class is not intended to be subclassed by clients. */ public class FixedThumbnail extends Figure implements UpdateListener { /** * This updates the Thumbnail by breaking the thumbnail {@link Image} into * several tiles and updating each tile individually. */ class ThumbnailUpdater implements Runnable { /** * The Constant MAX_BUFFER_SIZE. */ static final int MAX_BUFFER_SIZE = 256; private int currentHTile, currentVTile; private int hTiles, vTiles; private boolean isActive = true; private boolean isRunning = false; private GC thumbnailGC; private Graphics thumbnailGraphics; private Dimension tileSize; /** * Stops the updater and disposes of any resources. */ public void deactivate() { setActive(false); stop(); if (thumbnailImage != null) { thumbnailImage.dispose(); thumbnailImage = null; thumbnailImageSize = null; } } /** * Returns the current horizontal tile index. * * @return current horizontal tile index. */ protected int getCurrentHTile() { return currentHTile; } /** * Returns the current vertical tile index. * * @return current vertical tile index. */ protected int getCurrentVTile() { return currentVTile; } /** * Returns <code>true</code> if this ThumbnailUpdater is active. An * inactive updater has disposed of its {@link Image}. The updater may * be active and not currently running. * * @return <code>true</code> if this ThumbnailUpdater is active */ public boolean isActive() { return isActive; } /** * Returns <code>true</code> if this is currently running and updating * at least one tile on the thumbnail {@link Image}. * * @return <code>true</code> if this is currently running */ public boolean isRunning() { return isRunning; } /** * Resets the number of vertical and horizontal tiles, as well as the * tile size and current tile index. */ public void resetTileValues() { hTiles = (int) Math.ceil((float) getSourceRectangle().width / (float) MAX_BUFFER_SIZE); vTiles = (int) Math.ceil((float) getSourceRectangle().height / (float) MAX_BUFFER_SIZE); tileSize = new Dimension((int) Math.ceil((float) getSourceRectangle().width / (float) hTiles), (int) Math .ceil((float) getSourceRectangle().height / (float) vTiles)); currentHTile = 0; currentVTile = 0; } /** * Restarts the updater. */ public void restart() { stop(); start(); } /** * Updates the current tile on the Thumbnail. An area of the source * Figure is painted to an {@link Image}. That Image is then drawn on * the Thumbnail. Scaling of the source Image is done inside * {@link GC#drawImage(Image, int, int, int, int, int, int, int, int)} * since the source and target sizes are different. The current tile * indexes are incremented and if more updating is necesary, this * {@link Runnable} is called again in a * {@link Display#timerExec(int, Runnable)}. If no more updating is * required, {@link #stop()} is called. */ public void run() { if (!isActive() || !isRunning()) return; int v = getCurrentVTile(); int sy1 = v * tileSize.height; int sy2 = Math.min((v + 1) * tileSize.height, getSourceRectangle().height); int h = getCurrentHTile(); int sx1 = h * tileSize.width; int sx2 = Math.min((h + 1) * tileSize.width, getSourceRectangle().width); org.eclipse.draw2d.geometry.Point p = getSourceRectangle().getLocation(); Rectangle rect = new Rectangle(sx1 + p.x, sy1 + p.y, sx2 - sx1, sy2 - sy1); // CHANGED START // use the complete rectangle, not just a tile rect = getSourceRectangle().getCopy(); // CHANGED END thumbnailGraphics.pushState(); thumbnailGraphics.setClip(rect); thumbnailGraphics.fillRectangle(rect); try { sourceFigure.paint(thumbnailGraphics); } catch (SWTException e) { } catch (IllegalArgumentException e) { } thumbnailGraphics.popState(); if (getCurrentHTile() < (hTiles - 1)) setCurrentHTile(getCurrentHTile() + 1); else { setCurrentHTile(0); if (getCurrentVTile() < (vTiles - 1)) setCurrentVTile(getCurrentVTile() + 1); else setCurrentVTile(0); } // CHANGED START // do not paint multiple tiles, instead just draw the complete // rectangle once // if (getCurrentHTile() != 0 || getCurrentVTile() != 0) // Display.getCurrent().asyncExec(this); // else if (isDirty()) { // setDirty(false); // Display.getCurrent().asyncExec(this); // repaint(); // } else { // stop(); // repaint(); // } stop(); repaint(); // CHANGED END } /** * Sets the active flag. * * @param value * The active value */ public void setActive(boolean value) { isActive = value; } /** * Sets the current horizontal tile index. * * @param count * current horizontal tile index */ protected void setCurrentHTile(int count) { currentHTile = count; } /** * Sets the current vertical tile index. * * @param count * current vertical tile index */ protected void setCurrentVTile(int count) { currentVTile = count; } /** * Starts this updater. This method initializes all the necessary * resources and puts this {@link Runnable} on the asynch queue. If this * updater is not active or is already running, this method just * returns. */ public void start() { if (!isActive() || isRunning()) return; isRunning = true; setDirty(false); resetTileValues(); if (!targetSize.equals(thumbnailImageSize)) { resetThumbnailImage(); } if (targetSize.isEmpty()) return; thumbnailGC = new GC(thumbnailImage, sourceFigure.isMirrored() ? SWT.RIGHT_TO_LEFT : SWT.NONE); thumbnailGraphics = new FixedScaledGraphics(new SWTGraphics(thumbnailGC)); thumbnailGraphics.scale(getScaleX()); thumbnailGraphics.translate(getSourceRectangle().getLocation().negate()); Color color = sourceFigure.getForegroundColor(); if (color != null) thumbnailGraphics.setForegroundColor(color); color = sourceFigure.getBackgroundColor(); if (color != null) thumbnailGraphics.setBackgroundColor(color); thumbnailGraphics.setFont(sourceFigure.getFont()); setScales(targetSize.width / (float) getSourceRectangle().width, targetSize.height / (float) getSourceRectangle().height); Display.getCurrent().asyncExec(this); } /** * * @since 3.2 */ private void resetThumbnailImage() { if (thumbnailImage != null) { thumbnailImage.dispose(); } if (!targetSize.isEmpty()) { if (thumbnailImage != null && !thumbnailImage.isDisposed()) { thumbnailImage.dispose(); } thumbnailImage = new Image(Display.getDefault(), targetSize.width, targetSize.height); thumbnailImageSize = new Dimension(targetSize); } else { thumbnailImage = null; thumbnailImageSize = new Dimension(0, 0); } } /** * Stops this updater. Also disposes of resources (except the thumbnail * image which is still needed for painting). */ public void stop() { isRunning = false; if (thumbnailGraphics != null) { thumbnailGraphics.dispose(); thumbnailGraphics = null; } if (thumbnailGC != null) { thumbnailGC.dispose(); thumbnailGC = null; } // Don't dispose of the thumbnail image since it is needed to paint // the // figure when the source is not dirty (i.e. showing/hiding the // dock). } } private boolean isDirty; private float scaleX; private float scaleY; private IFigure sourceFigure; /** * The target size. */ Dimension targetSize = new Dimension(0, 0); private Image thumbnailImage; private Dimension thumbnailImageSize; private ThumbnailUpdater updater = new ThumbnailUpdater(); /** * Creates a new Thumbnail. The source Figure must be set separately if you * use this constructor. */ public FixedThumbnail() { super(); } /** * Creates a new Thumbnail with the given IFigure as its source figure. * * @param fig * The source figure */ public FixedThumbnail(IFigure fig) { this(); setSource(fig); } private Dimension adjustToAspectRatio(Dimension size, boolean adjustToMaxDimension) { Dimension sourceSize = getSourceRectangle().getSize(); Dimension borderSize = new Dimension(getInsets().getWidth(), getInsets().getHeight()); size.expand(borderSize.getNegated()); int width, height; if (adjustToMaxDimension) { width = Math.max(size.width, (int) (size.height * sourceSize.width / (float) sourceSize.height + 0.5)); height = Math.max(size.height, (int) (size.width * sourceSize.height / (float) sourceSize.width + 0.5)); } else { width = Math.min(size.width, (int) (size.height * sourceSize.width / (float) sourceSize.height + 0.5)); height = Math.min(size.height, (int) (size.width * sourceSize.height / (float) sourceSize.width + 0.5)); } size.width = width; size.height = height; return size.expand(borderSize); } /** * Deactivates this Thumbnail. */ public void deactivate() { sourceFigure.getUpdateManager().removeUpdateListener(this); updater.deactivate(); } /** * Returns the preferred size of this Thumbnail. The preferred size will be * calculated in a way that maintains the source Figure's aspect ratio. * * @param wHint * The width hint * @param hHint * The height hint * * @return The preferred size */ @Override public Dimension getPreferredSize(int wHint, int hHint) { if (prefSize == null) return adjustToAspectRatio(getBounds().getSize(), false); Dimension preferredSize = adjustToAspectRatio(prefSize.getCopy(), true); if (maxSize == null) return preferredSize; Dimension maximumSize = adjustToAspectRatio(maxSize.getCopy(), true); if (preferredSize.contains(maximumSize)) return maximumSize; else return preferredSize; } /** * Returns the scale factor on the X-axis. * * @return X scale */ protected float getScaleX() { return scaleX; } /** * Returns the scale factor on the Y-axis. * * @return Y scale */ protected float getScaleY() { return scaleY; } /** * Returns the source figure being used to generate a thumbnail. * * @return the source figure */ protected IFigure getSource() { return sourceFigure; } /** * Returns the rectangular region relative to the source figure which will * be the basis of the thumbnail. The value may be returned by reference and * should not be modified by the caller. * * @return the region of the source figure being used for the thumbnail * * @since 3.1 */ protected Rectangle getSourceRectangle() { return sourceFigure.getBounds(); } /** * Returns the scaled Image of the source Figure. If the Image needs to be * updated, the ThumbnailUpdater will notified. * * @return The thumbnail image */ protected Image getThumbnailImage() { Dimension oldSize = targetSize; targetSize = getPreferredSize(); targetSize.expand(new Dimension(getInsets().getWidth(), getInsets().getHeight()).negate()); setScales(targetSize.width / (float) getSourceRectangle().width, targetSize.height / (float) getSourceRectangle().height); if ((isDirty()) && !updater.isRunning()) updater.start(); else if (oldSize != null && !targetSize.equals(oldSize)) { revalidate(); updater.restart(); } return thumbnailImage; } /** * Returns <code>true</code> if the source figure has changed. * * @return <code>true</code> if the source figure has changed */ protected boolean isDirty() { return isDirty; } /** * Notify painting. * * @param damage * the damage * @param dirtyRegions * the dirty regions * * @see org.eclipse.draw2d.UpdateListener#notifyPainting(Rectangle, Map) */ public void notifyPainting(Rectangle damage, @SuppressWarnings("rawtypes") Map dirtyRegions) { Iterator<?> dirtyFigures = dirtyRegions.keySet().iterator(); while (dirtyFigures.hasNext()) { IFigure current = (IFigure) dirtyFigures.next(); while (current != null) { if (current == getSource()) { setDirty(true); repaint(); return; } current = current.getParent(); } } } /** * Notify validating. * * @see org.eclipse.draw2d.UpdateListener#notifyValidating() */ public void notifyValidating() { // setDirty(true); // revalidate(); } /** * Paint figure. * * @param graphics * the graphics * * @see org.eclipse.draw2d.Figure#paintFigure(Graphics) */ @Override protected void paintFigure(Graphics graphics) { Image thumbnail = getThumbnailImage(); if (thumbnail == null) return; graphics.drawImage(thumbnail, getClientArea().getLocation()); } /** * Sets the dirty flag. * * @param value * The dirty value */ public void setDirty(boolean value) { isDirty = value; } /** * Sets the X and Y scales for the Thumbnail. These scales represent the * ratio between the source figure and the Thumbnail. * * @param x * The X scale * @param y * The Y scale */ protected void setScales(float x, float y) { scaleX = x; scaleY = y; } /** * Sets the source Figure. Also sets the scales and creates the necessary * update manager. * * @param fig * The source figure */ public void setSource(IFigure fig) { if (sourceFigure == fig) return; if (sourceFigure != null) sourceFigure.getUpdateManager().removeUpdateListener(this); sourceFigure = fig; if (sourceFigure != null) { setScales((float) getSize().width / (float) getSourceRectangle().width, (float) getSize().height / (float) getSourceRectangle().height); sourceFigure.getUpdateManager().addUpdateListener(this); repaint(); } } }