blob: f1229aab1e8c150e81193456d65d102b8e2b03fe [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2007 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: University of Victoria - Adapted for XY Scaled Graphics
*******************************************************************************/
package org.eclipse.zest.core.widgets.internal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.eclipse.draw2d.FigureUtilities;
import org.eclipse.draw2d.Graphics;
import org.eclipse.draw2d.ScaledGraphics;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.PointList;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.FontMetrics;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.TextLayout;
import org.eclipse.swt.graphics.TextStyle;
import org.eclipse.swt.widgets.Display;
/**
* This was adapted from the ScaledGraphics class to allow X and Y to scale
* independently. It won't require this level of coupling if some of these
* private methods were made protected. I will open a bug report on this.
*
* @author irbull
*/
public class XYScaledGraphics extends ScaledGraphics {
public static final double MAX_TEXT_SIZE = 0.45; // MAX size, when to stop zooming text
private static class FontHeightCache {
Font font;
int height;
}
static class FontKey {
Font font;
int height;
protected FontKey() {
}
protected FontKey(Font font, int height) {
this.font = font;
this.height = height;
}
public boolean equals(Object obj) {
return (((FontKey) obj).font.equals(font) && ((FontKey) obj).height == height);
}
public int hashCode() {
return font.hashCode() ^ height;
}
protected void setValues(Font font, int height) {
this.font = font;
this.height = height;
}
}
/**
* The internal state of the scaled graphics.
*/
protected static class State {
private double appliedX;
private double appliedY;
private Font font;
private int lineWidth;
//private double zoom; // This has been replaced with xZoom and yZoom
private double xZoom;
private double yZoom;
/**
* Constructs a new, uninitialized State object.
*/
protected State() {
}
/**
* Constructs a new State object and initializes the properties based on
* the given values.
*
* @param zoom
* the zoom factor
* @param x
* the x offset
* @param y
* the y offset
* @param font
* the font
* @param lineWidth
* the line width
*/
protected State(double xZoom, double yZoom, double x, double y, Font font, int lineWidth) {
this.xZoom = xZoom;
this.yZoom = yZoom;
this.appliedX = x;
this.appliedY = y;
this.font = font;
this.lineWidth = lineWidth;
}
/**
* Sets all the properties of the state object.
*
* @param zoom
* the zoom factor
* @param x
* the x offset
* @param y
* the y offset
* @param font
* the font
* @param lineWidth
* the line width
*/
protected void setValues(double xZoom, double yZoom, double x, double y, Font font, int lineWidth) {
this.xZoom = xZoom;
this.yZoom = yZoom;
this.appliedX = x;
this.appliedY = y;
this.font = font;
this.lineWidth = lineWidth;
}
}
private static int[][] intArrayCache = new int[8][];
private final Rectangle tempRECT = new Rectangle();
static {
for (int i = 0; i < intArrayCache.length; i++) {
intArrayCache[i] = new int[i + 1];
}
}
private boolean allowText = true;
// private static final Point PT = new Point();
private Map fontCache = new HashMap();
private Map fontDataCache = new HashMap();
private FontKey fontKey = new FontKey();
private double fractionalX;
private double fractionalY;
private Graphics graphics;
private FontHeightCache localCache = new FontHeightCache();
private Font localFont;
private int localLineWidth;
private List stack = new ArrayList();
private int stackPointer = 0;
private FontHeightCache targetCache = new FontHeightCache();
double xZoom = 1.0;
double yZoom = 1.0;
/**
* Constructs a new ScaledGraphics based on the given Graphics object.
*
* @param g
* the base graphics object
*/
public XYScaledGraphics(Graphics g) {
super(g);
graphics = g;
localFont = g.getFont();
localLineWidth = g.getLineWidth();
}
/** @see Graphics#clipRect(Rectangle) */
public void clipRect(Rectangle r) {
graphics.clipRect(zoomClipRect(r));
}
Font createFont(FontData data) {
return new Font(Display.getCurrent(), data);
}
/** @see Graphics#dispose() */
public void dispose() {
//Remove all states from the stack
while (stackPointer > 0) {
popState();
}
//Dispose fonts
Iterator iter = fontCache.values().iterator();
while (iter.hasNext()) {
Font font = ((Font) iter.next());
font.dispose();
}
}
/** @see Graphics#drawArc(int, int, int, int, int, int) */
public void drawArc(int x, int y, int w, int h, int offset, int sweep) {
Rectangle z = zoomRect(x, y, w, h);
if (z.isEmpty() || sweep == 0) {
return;
}
graphics.drawArc(z, offset, sweep);
}
/** @see Graphics#drawFocus(int, int, int, int) */
public void drawFocus(int x, int y, int w, int h) {
graphics.drawFocus(zoomRect(x, y, w, h));
}
/** @see Graphics#drawImage(Image, int, int) */
public void drawImage(Image srcImage, int x, int y) {
org.eclipse.swt.graphics.Rectangle size = srcImage.getBounds();
double imageZoom = Math.min(xZoom, yZoom);
graphics.drawImage(srcImage, 0, 0, size.width, size.height, (int) (Math.floor((x * xZoom + fractionalX))), (int) (Math.floor((y * yZoom + fractionalY))), (int) (Math.floor((size.width * imageZoom + fractionalX))), (int) (Math.floor((size.height * imageZoom + fractionalY))));
}
/** @see Graphics#drawImage(Image, int, int, int, int, int, int, int, int) */
public void drawImage(Image srcImage, int sx, int sy, int sw, int sh, int tx, int ty, int tw, int th) {
//"t" == target rectangle, "s" = source
Rectangle t = zoomRect(tx, ty, tw, th);
if (!t.isEmpty()) {
graphics.drawImage(srcImage, sx, sy, sw, sh, t.x, t.y, t.width, t.height);
}
}
/** @see Graphics#drawLine(int, int, int, int) */
public void drawLine(int x1, int y1, int x2, int y2) {
graphics.drawLine((int) (Math.floor((x1 * xZoom + fractionalX))), (int) (Math.floor((y1 * yZoom + fractionalY))), (int) (Math.floor((x2 * xZoom + fractionalX))), (int) (Math.floor((y2 * yZoom + fractionalY))));
}
/** @see Graphics#drawOval(int, int, int, int) */
public void drawOval(int x, int y, int w, int h) {
graphics.drawOval(zoomRect(x, y, w, h));
}
/** @see Graphics#drawPoint(int, int) */
public void drawPoint(int x, int y) {
graphics.drawPoint((int) Math.floor(x * xZoom + fractionalX), (int) Math.floor(y * yZoom + fractionalY));
}
/**
* @see Graphics#drawPolygon(int[])
*/
public void drawPolygon(int[] points) {
graphics.drawPolygon(zoomPointList(points));
}
/** @see Graphics#drawPolygon(PointList) */
public void drawPolygon(PointList points) {
graphics.drawPolygon(zoomPointList(points.toIntArray()));
}
/**
* @see Graphics#drawPolyline(int[])
*/
public void drawPolyline(int[] points) {
graphics.drawPolyline(zoomPointList(points));
}
/** @see Graphics#drawPolyline(PointList) */
public void drawPolyline(PointList points) {
graphics.drawPolyline(zoomPointList(points.toIntArray()));
}
/** @see Graphics#drawRectangle(int, int, int, int) */
public void drawRectangle(int x, int y, int w, int h) {
graphics.drawRectangle(zoomRect(x, y, w, h));
}
/** @see Graphics#drawRoundRectangle(Rectangle, int, int) */
public void drawRoundRectangle(Rectangle r, int arcWidth, int arcHeight) {
graphics.drawRoundRectangle(zoomRect(r.x, r.y, r.width, r.height), (int) (arcWidth * xZoom), (int) (arcHeight * yZoom));
}
/** @see Graphics#drawString(String, int, int) */
public void drawString(String s, int x, int y) {
if (allowText) {
graphics.drawString(s, zoomTextPoint(x, y));
}
}
/** @see Graphics#drawText(String, int, int) */
public void drawText(String s, int x, int y) {
if (allowText) {
graphics.drawText(s, zoomTextPoint(x, y));
}
}
/**
* @see Graphics#drawText(String, int, int, int)
*/
public void drawText(String s, int x, int y, int style) {
if (allowText) {
graphics.drawText(s, zoomTextPoint(x, y), style);
}
}
/**
* @see Graphics#drawTextLayout(TextLayout, int, int, int, int, Color,
* Color)
*/
public void drawTextLayout(TextLayout layout, int x, int y, int selectionStart, int selectionEnd, Color selectionForeground, Color selectionBackground) {
TextLayout scaled = zoomTextLayout(layout);
graphics.drawTextLayout(scaled, (int) Math.floor(x * xZoom + fractionalX), (int) Math.floor(y * yZoom + fractionalY), selectionStart, selectionEnd, selectionBackground, selectionForeground);
scaled.dispose();
}
/** @see Graphics#fillArc(int, int, int, int, int, int) */
public void fillArc(int x, int y, int w, int h, int offset, int sweep) {
Rectangle z = zoomFillRect(x, y, w, h);
if (z.isEmpty() || sweep == 0) {
return;
}
graphics.fillArc(z, offset, sweep);
}
/** @see Graphics#fillGradient(int, int, int, int, boolean) */
public void fillGradient(int x, int y, int w, int h, boolean vertical) {
graphics.fillGradient(zoomFillRect(x, y, w, h), vertical);
}
/** @see Graphics#fillOval(int, int, int, int) */
public void fillOval(int x, int y, int w, int h) {
graphics.fillOval(zoomFillRect(x, y, w, h));
}
/**
* @see Graphics#fillPolygon(int[])
*/
public void fillPolygon(int[] points) {
graphics.fillPolygon(zoomPointList(points));
}
/** @see Graphics#fillPolygon(PointList) */
public void fillPolygon(PointList points) {
graphics.fillPolygon(zoomPointList(points.toIntArray()));
}
/** @see Graphics#fillRectangle(int, int, int, int) */
public void fillRectangle(int x, int y, int w, int h) {
graphics.fillRectangle(zoomFillRect(x, y, w, h));
}
/** @see Graphics#fillRoundRectangle(Rectangle, int, int) */
public void fillRoundRectangle(Rectangle r, int arcWidth, int arcHeight) {
graphics.fillRoundRectangle(zoomFillRect(r.x, r.y, r.width, r.height), (int) (arcWidth * xZoom), (int) (arcHeight * yZoom));
}
/** @see Graphics#fillString(String, int, int) */
public void fillString(String s, int x, int y) {
if (allowText) {
graphics.fillString(s, zoomTextPoint(x, y));
}
}
/** @see Graphics#fillText(String, int, int) */
public void fillText(String s, int x, int y) {
if (allowText) {
graphics.fillText(s, zoomTextPoint(x, y));
}
}
/**
* @see Graphics#getAbsoluteScale()
*/
public double getAbsoluteScale() {
return xZoom * graphics.getAbsoluteScale();
}
/**
* @see Graphics#getAlpha()
*/
public int getAlpha() {
return graphics.getAlpha();
}
/**
* @see Graphics#getAntialias()
*/
public int getAntialias() {
return graphics.getAntialias();
}
/** @see Graphics#getBackgroundColor() */
public Color getBackgroundColor() {
return graphics.getBackgroundColor();
}
Font getCachedFont(FontKey key) {
Font font = (Font) fontCache.get(key);
if (font != null) {
return font;
}
key = new FontKey(key.font, key.height);
FontData data = key.font.getFontData()[0];
data.setHeight(key.height);
Font zoomedFont = createFont(data);
fontCache.put(key, zoomedFont);
return zoomedFont;
}
FontData getCachedFontData(Font f) {
FontData data = (FontData) fontDataCache.get(f);
if (data != null) {
return data;
}
data = getLocalFont().getFontData()[0];
fontDataCache.put(f, data);
return data;
}
/** @see Graphics#getClip(Rectangle) */
public Rectangle getClip(Rectangle rect) {
graphics.getClip(rect);
int x = (int) (rect.x / xZoom);
int y = (int) (rect.y / yZoom);
/*
* If the clip rectangle is queried, perform an inverse zoom, and take
* the ceiling of the resulting double. This is necessary because
* forward scaling essentially performs a floor() function. Without
* this, figures will think that they don't need to paint when actually
* they do.
*/
rect.width = (int) Math.ceil(rect.right() / xZoom) - x;
rect.height = (int) Math.ceil(rect.bottom() / yZoom) - y;
rect.x = x;
rect.y = y;
return rect;
}
/**
* @see Graphics#getFillRule()
*/
public int getFillRule() {
return graphics.getFillRule();
}
/** @see Graphics#getFont() */
public Font getFont() {
return getLocalFont();
}
/** @see Graphics#getFontMetrics() */
public FontMetrics getFontMetrics() {
return FigureUtilities.getFontMetrics(localFont);
}
/** @see Graphics#getForegroundColor() */
public Color getForegroundColor() {
return graphics.getForegroundColor();
}
/**
* @see Graphics#getInterpolation()
*/
public int getInterpolation() {
return graphics.getInterpolation();
}
/**
* @see Graphics#getLineCap()
*/
public int getLineCap() {
return graphics.getLineCap();
}
/**
* @see Graphics#getLineJoin()
*/
public int getLineJoin() {
return graphics.getLineJoin();
}
/** @see Graphics#getLineStyle() */
public int getLineStyle() {
return graphics.getLineStyle();
}
/** @see Graphics#getLineWidth() */
public int getLineWidth() {
return getLocalLineWidth();
}
private Font getLocalFont() {
return localFont;
}
private int getLocalLineWidth() {
return localLineWidth;
}
/**
* @see Graphics#getTextAntialias()
*/
public int getTextAntialias() {
return graphics.getTextAntialias();
}
/** @see Graphics#getXORMode() */
public boolean getXORMode() {
return graphics.getXORMode();
}
/** @see Graphics#popState() */
public void popState() {
graphics.popState();
stackPointer--;
restoreLocalState((State) stack.get(stackPointer));
}
/** @see Graphics#pushState() */
public void pushState() {
State s;
if (stack.size() > stackPointer) {
s = (State) stack.get(stackPointer);
s.setValues(xZoom, yZoom, fractionalX, fractionalY, getLocalFont(), localLineWidth);
} else {
stack.add(new State(xZoom, yZoom, fractionalX, fractionalY, getLocalFont(), localLineWidth));
}
stackPointer++;
graphics.pushState();
}
private void restoreLocalState(State state) {
this.fractionalX = state.appliedX;
this.fractionalY = state.appliedY;
setScale(state.xZoom, state.yZoom);
setLocalFont(state.font);
setLocalLineWidth(state.lineWidth);
}
/** @see Graphics#restoreState() */
public void restoreState() {
graphics.restoreState();
restoreLocalState((State) stack.get(stackPointer - 1));
}
public void scale(double xAmount, double yAmount) {
setScale(xZoom * xAmount, yZoom * yAmount);
}
/** @see Graphics#scale(double) */
public void scale(double amount) {
//setScale(zoom * amount);
throw new RuntimeException("Operation not supported, use scale(x, y)");
}
/**
* @see Graphics#setAlpha(int)
*/
public void setAlpha(int alpha) {
graphics.setAlpha(alpha);
}
/**
* @see Graphics#setAntialias(int)
*/
public void setAntialias(int value) {
graphics.setAntialias(value);
}
/** @see Graphics#setBackgroundColor(Color) */
public void setBackgroundColor(Color rgb) {
graphics.setBackgroundColor(rgb);
}
/** @see Graphics#setClip(Rectangle) */
public void setClip(Rectangle r) {
graphics.setClip(zoomClipRect(r));
}
/**
* @see Graphics#setFillRule(int)
*/
public void setFillRule(int rule) {
graphics.setFillRule(rule);
}
/** @see Graphics#setFont(Font) */
public void setFont(Font f) {
setLocalFont(f);
}
/** @see Graphics#setForegroundColor(Color) */
public void setForegroundColor(Color rgb) {
graphics.setForegroundColor(rgb);
}
/**
* @see org.eclipse.draw2d.Graphics#setInterpolation(int)
*/
public void setInterpolation(int interpolation) {
graphics.setInterpolation(interpolation);
}
/**
* @see Graphics#setLineCap(int)
*/
public void setLineCap(int cap) {
graphics.setLineCap(cap);
}
/**
* @see Graphics#setLineDash(int[])
*/
public void setLineDash(int[] dash) {
graphics.setLineDash(dash);
}
/**
* @see Graphics#setLineJoin(int)
*/
public void setLineJoin(int join) {
graphics.setLineJoin(join);
}
/** @see Graphics#setLineStyle(int) */
public void setLineStyle(int style) {
graphics.setLineStyle(style);
}
/** @see Graphics#setLineWidth(int) */
public void setLineWidth(int width) {
setLocalLineWidth(width);
}
private void setLocalFont(Font f) {
localFont = f;
graphics.setFont(zoomFont(f));
}
private void setLocalLineWidth(int width) {
localLineWidth = width;
graphics.setLineWidth(zoomLineWidth(width));
}
public void setScale(double xValue, double yValue) {
if (xValue == xZoom && yValue == yZoom) {
return;
}
this.xZoom = xValue;
this.yZoom = yValue;
graphics.setFont(zoomFont(getLocalFont()));
graphics.setLineWidth(zoomLineWidth(localLineWidth));
}
void setScale(double value) {
throw new RuntimeException("Operation not supported, use setScale(x,y)");
/*
* if (zoom == value) return; this.zoom = value;
* graphics.setFont(zoomFont(getLocalFont()));
* graphics.setLineWidth(zoomLineWidth(localLineWidth));
*/
}
/**
* @see Graphics#setTextAntialias(int)
*/
public void setTextAntialias(int value) {
graphics.setTextAntialias(value);
}
/** @see Graphics#setXORMode(boolean) */
public void setXORMode(boolean b) {
graphics.setXORMode(b);
}
/** @see Graphics#translate(int, int) */
public void translate(int dx, int dy) {
// fractionalX/Y is the fractional part left over from previous
// translates that gets lost in the integer approximation.
double dxFloat = dx * xZoom + fractionalX;
double dyFloat = dy * yZoom + fractionalY;
fractionalX = dxFloat - Math.floor(dxFloat);
fractionalY = dyFloat - Math.floor(dyFloat);
graphics.translate((int) Math.floor(dxFloat), (int) Math.floor(dyFloat));
}
private Rectangle zoomClipRect(Rectangle r) {
tempRECT.x = (int) (Math.floor(r.x * xZoom + fractionalX));
tempRECT.y = (int) (Math.floor(r.y * yZoom + fractionalY));
tempRECT.width = (int) (Math.ceil(((r.x + r.width) * xZoom + fractionalX))) - tempRECT.x;
tempRECT.height = (int) (Math.ceil(((r.y + r.height) * yZoom + fractionalY))) - tempRECT.y;
return tempRECT;
}
private Rectangle zoomFillRect(int x, int y, int w, int h) {
tempRECT.x = (int) (Math.floor((x * xZoom + fractionalX)));
tempRECT.y = (int) (Math.floor((y * yZoom + fractionalY)));
tempRECT.width = (int) (Math.floor(((x + w - 1) * xZoom + fractionalX))) - tempRECT.x + 1;
tempRECT.height = (int) (Math.floor(((y + h - 1) * yZoom + fractionalY))) - tempRECT.y + 1;
return tempRECT;
}
Font zoomFont(Font f) {
if (f == null) {
f = Display.getCurrent().getSystemFont();
}
FontData data = getCachedFontData(f);
int zoomedFontHeight = zoomFontHeight(data.getHeight());
allowText = zoomedFontHeight > 0;
fontKey.setValues(f, zoomedFontHeight);
return getCachedFont(fontKey);
}
int zoomFontHeight(int height) {
double tmp = Math.min(yZoom, xZoom);
if (tmp < MAX_TEXT_SIZE) {
return (int) (tmp * height);
} else {
return (int) (height * tmp);
}
}
int zoomLineWidth(int w) {
return w;
}
private int[] zoomPointList(int[] points) {
int[] scaled = null;
// Look in cache for a integer array with the same length as 'points'
for (int i = 0; i < intArrayCache.length; i++) {
if (intArrayCache[i].length == points.length) {
scaled = intArrayCache[i];
// Move this integer array up one notch in the array
if (i != 0) {
int[] temp = intArrayCache[i - 1];
intArrayCache[i - 1] = scaled;
intArrayCache[i] = temp;
}
}
}
// If no match is found, take the one that is last and resize it.
if (scaled == null) {
intArrayCache[intArrayCache.length - 1] = new int[points.length];
scaled = intArrayCache[intArrayCache.length - 1];
}
// Scale the points
for (int i = 0; (i + 1) < points.length; i += 2) {
scaled[i] = (int) (Math.floor((points[i] * xZoom + fractionalX)));
scaled[i + 1] = (int) (Math.floor((points[i + 1] * yZoom + fractionalY)));
}
return scaled;
}
private Rectangle zoomRect(int x, int y, int w, int h) {
tempRECT.x = (int) (Math.floor(x * xZoom + fractionalX));
tempRECT.y = (int) (Math.floor(y * yZoom + fractionalY));
tempRECT.width = (int) (Math.floor(((x + w) * xZoom + fractionalX))) - tempRECT.x;
tempRECT.height = (int) (Math.floor(((y + h) * yZoom + fractionalY))) - tempRECT.y;
return tempRECT;
}
private TextLayout zoomTextLayout(TextLayout layout) {
TextLayout zoomed = new TextLayout(Display.getCurrent());
zoomed.setText(layout.getText());
int zoomWidth = -1;
if (layout.getWidth() != -1) {
zoomWidth = ((int) (layout.getWidth() * xZoom));
}
if (zoomWidth < -1 || zoomWidth == 0) {
return null;
}
zoomed.setFont(zoomFont(layout.getFont()));
zoomed.setAlignment(layout.getAlignment());
zoomed.setAscent(layout.getAscent());
zoomed.setDescent(layout.getDescent());
zoomed.setOrientation(layout.getOrientation());
zoomed.setSegments(layout.getSegments());
zoomed.setSpacing(layout.getSpacing());
zoomed.setTabs(layout.getTabs());
zoomed.setWidth(zoomWidth);
int length = layout.getText().length();
if (length > 0) {
int start = 0, offset = 1;
TextStyle style = null, lastStyle = layout.getStyle(0);
for (; offset <= length; offset++) {
if (offset != length && (style = layout.getStyle(offset)) == lastStyle) {
continue;
}
int end = offset - 1;
if (lastStyle != null) {
TextStyle zoomedStyle = new TextStyle(zoomFont(lastStyle.font), lastStyle.foreground, lastStyle.background);
zoomed.setStyle(zoomedStyle, start, end);
}
lastStyle = style;
start = offset;
}
}
return zoomed;
}
private Point zoomTextPoint(int x, int y) {
if (localCache.font != localFont) {
//Font is different, re-calculate its height
FontMetrics metric = FigureUtilities.getFontMetrics(localFont);
// UNSUPPORTED - getDescent() not implemented in RAP
// localCache.height = metric.getHeight() - metric.getDescent();
localCache.height = metric.getHeight();
localCache.font = localFont;
}
if (targetCache.font != graphics.getFont()) {
FontMetrics metric = graphics.getFontMetrics();
targetCache.font = graphics.getFont();
// UNSUPPORTED - getDescent() not implemented in RAP
// targetCache.height = metric.getHeight() - metric.getDescent();
targetCache.height = metric.getHeight();
}
return new Point(((int) (Math.floor((x * xZoom) + fractionalX))), (int) (Math.floor((y + localCache.height - 1) * yZoom - targetCache.height + 1 + fractionalY)));
}
}