blob: cfb1b644096faf77de8e1ec324b09f147b50b509 [file] [log] [blame]
/*******************************************************************************
* 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.parts;
import java.util.Iterator;
import java.util.Map;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.PaletteData;
import org.eclipse.swt.widgets.Display;
import org.eclipse.draw2d.Figure;
import org.eclipse.draw2d.Graphics;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.SWTGraphics;
import org.eclipse.draw2d.ScaledGraphics;
import org.eclipse.draw2d.UpdateListener;
import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.draw2d.rap.swt.SWT;
/**
* A Thumbnail is a Figure that displays an image of its source Figure at a
* smaller size. The Thumbnail will maintain the aspect ratio of the source
* Figure.
*
* @author Eric Bordeau
*/
public class Thumbnail 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 {
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 ScaledGraphics 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() || thumbnailGraphics == null)
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);
thumbnailGraphics.pushState();
thumbnailGraphics.setClip(rect);
thumbnailGraphics.fillRectangle(rect);
sourceFigure.paint(thumbnailGraphics);
thumbnailGraphics.popState();
if (getCurrentHTile() < (hTiles - 1))
setCurrentHTile(getCurrentHTile() + 1);
else {
setCurrentHTile(0);
if (getCurrentVTile() < (vTiles - 1))
setCurrentVTile(getCurrentVTile() + 1);
else
setCurrentVTile(0);
}
if (getCurrentHTile() != 0 || getCurrentVTile() != 0)
Display.getCurrent().asyncExec(this);
else if (isDirty()) {
setDirty(false);
Display.getCurrent().asyncExec(this);
repaint();
} else {
stop();
repaint();
}
}
/**
* 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;
// UNSUPPORTED - image constructor not implemented in RAP
// thumbnailGC = new GC(thumbnailImage,
// sourceFigure.isMirrored() ? SWT.RIGHT_TO_LEFT : SWT.NONE);
thumbnailGC = new GC(thumbnailImage.getDevice(),
sourceFigure.isMirrored() ? SWT.RIGHT_TO_LEFT : SWT.NONE);
thumbnailGraphics = new ScaledGraphics(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()) {
// UNSUPPORTED - original image constructor not implemented in
// RAP
// Image creation as supported in RAP
thumbnailImage = new Image(Display.getDefault(), new ImageData(
targetSize.width, targetSize.height, 32,
new PaletteData(127, 127, 127)));
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 (thumbnailGC != null) {
thumbnailGC.dispose();
thumbnailGC = null;
}
if (thumbnailGraphics != null) {
thumbnailGraphics.dispose();
thumbnailGraphics = 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;
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 Thumbnail() {
super();
}
/**
* Creates a new Thumbnail with the given IFigure as its source figure.
*
* @param fig
* The source figure
*/
public Thumbnail(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
*/
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.
*
* @since 3.1
* @return the region of the source figure being used for the thumbnail
*/
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;
}
/**
* @see org.eclipse.draw2d.UpdateListener#notifyPainting(Rectangle, Map)
*/
public void notifyPainting(Rectangle damage, 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();
}
}
}
/**
* @see org.eclipse.draw2d.UpdateListener#notifyValidating()
*/
public void notifyValidating() {
// setDirty(true);
// revalidate();
}
/**
* @see org.eclipse.draw2d.Figure#paintFigure(Graphics)
*/
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();
}
}
}