blob: 354517e7658f50ef6b2a7cf06bdca923d6fe2692 [file] [log] [blame]
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.eclipse.org/org/documents/epl-v10.php
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.ide.eclipse.adt.internal.editors.layout.gle2;
import com.android.annotations.NonNull;
import com.android.ide.common.api.DrawingStyle;
import com.android.ide.common.api.IColor;
import com.android.ide.common.api.IGraphics;
import com.android.ide.common.api.IViewRule;
import com.android.ide.common.api.Point;
import com.android.ide.common.api.Rect;
import org.eclipse.swt.SWT;
import org.eclipse.swt.SWTException;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.FontMetrics;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.RGB;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Wraps an SWT {@link GC} into an {@link IGraphics} interface so that {@link IViewRule} objects
* can directly draw on the canvas.
* <p/>
* The actual wrapped GC object is only non-null during the context of a paint operation.
*/
public class GCWrapper implements IGraphics {
/**
* The actual SWT {@link GC} being wrapped. This can change during the lifetime of the
* object. It is generally set to something during an onPaint method and then changed
* to null when not in the context of a paint.
*/
private GC mGc;
/**
* Current style being used for drawing.
*/
private SwtDrawingStyle mCurrentStyle = SwtDrawingStyle.INVALID;
/**
* Implementation of IColor wrapping an SWT color.
*/
private static class ColorWrapper implements IColor {
private final Color mColor;
public ColorWrapper(Color color) {
mColor = color;
}
public Color getColor() {
return mColor;
}
}
/** A map of registered colors. All these colors must be disposed at the end. */
private final HashMap<Integer, ColorWrapper> mColorMap = new HashMap<Integer, ColorWrapper>();
/**
* A map of the {@link SwtDrawingStyle} stroke colors that we have actually
* used (to be disposed)
*/
private final Map<DrawingStyle, Color> mStyleStrokeMap = new EnumMap<DrawingStyle, Color>(
DrawingStyle.class);
/**
* A map of the {@link SwtDrawingStyle} fill colors that we have actually
* used (to be disposed)
*/
private final Map<DrawingStyle, Color> mStyleFillMap = new EnumMap<DrawingStyle, Color>(
DrawingStyle.class);
/** The cached pixel height of the default current font. */
private int mFontHeight = 0;
/** The scaling of the canvas in X. */
private final CanvasTransform mHScale;
/** The scaling of the canvas in Y. */
private final CanvasTransform mVScale;
public GCWrapper(CanvasTransform hScale, CanvasTransform vScale) {
mHScale = hScale;
mVScale = vScale;
mGc = null;
}
void setGC(GC gc) {
mGc = gc;
}
private GC getGc() {
return mGc;
}
void checkGC() {
if (mGc == null) {
throw new RuntimeException("IGraphics used without a valid context.");
}
}
void dispose() {
for (ColorWrapper c : mColorMap.values()) {
c.getColor().dispose();
}
mColorMap.clear();
for (Color c : mStyleStrokeMap.values()) {
c.dispose();
}
mStyleStrokeMap.clear();
for (Color c : mStyleFillMap.values()) {
c.dispose();
}
mStyleFillMap.clear();
}
//-------------
@Override
public @NonNull IColor registerColor(int rgb) {
checkGC();
Integer key = Integer.valueOf(rgb);
ColorWrapper c = mColorMap.get(key);
if (c == null) {
c = new ColorWrapper(new Color(getGc().getDevice(),
(rgb >> 16) & 0xFF,
(rgb >> 8) & 0xFF,
(rgb >> 0) & 0xFF));
mColorMap.put(key, c);
}
return c;
}
/** Returns the (cached) pixel height of the current font. */
@Override
public int getFontHeight() {
if (mFontHeight < 1) {
checkGC();
FontMetrics fm = getGc().getFontMetrics();
mFontHeight = fm.getHeight();
}
return mFontHeight;
}
@Override
public @NonNull IColor getForeground() {
Color c = getGc().getForeground();
return new ColorWrapper(c);
}
@Override
public @NonNull IColor getBackground() {
Color c = getGc().getBackground();
return new ColorWrapper(c);
}
@Override
public int getAlpha() {
return getGc().getAlpha();
}
@Override
public void setForeground(@NonNull IColor color) {
checkGC();
getGc().setForeground(((ColorWrapper) color).getColor());
}
@Override
public void setBackground(@NonNull IColor color) {
checkGC();
getGc().setBackground(((ColorWrapper) color).getColor());
}
@Override
public void setAlpha(int alpha) {
checkGC();
try {
getGc().setAlpha(alpha);
} catch (SWTException e) {
// This means that we cannot set the alpha on this platform; this is
// an acceptable no-op.
}
}
@Override
public void setLineStyle(@NonNull LineStyle style) {
int swtStyle = 0;
switch (style) {
case LINE_SOLID:
swtStyle = SWT.LINE_SOLID;
break;
case LINE_DASH:
swtStyle = SWT.LINE_DASH;
break;
case LINE_DOT:
swtStyle = SWT.LINE_DOT;
break;
case LINE_DASHDOT:
swtStyle = SWT.LINE_DASHDOT;
break;
case LINE_DASHDOTDOT:
swtStyle = SWT.LINE_DASHDOTDOT;
break;
default:
assert false : style;
break;
}
if (swtStyle != 0) {
checkGC();
getGc().setLineStyle(swtStyle);
}
}
@Override
public void setLineWidth(int width) {
checkGC();
if (width > 0) {
getGc().setLineWidth(width);
}
}
// lines
@Override
public void drawLine(int x1, int y1, int x2, int y2) {
checkGC();
useStrokeAlpha();
x1 = mHScale.translate(x1);
y1 = mVScale.translate(y1);
x2 = mHScale.translate(x2);
y2 = mVScale.translate(y2);
getGc().drawLine(x1, y1, x2, y2);
}
@Override
public void drawLine(@NonNull Point p1, @NonNull Point p2) {
drawLine(p1.x, p1.y, p2.x, p2.y);
}
// rectangles
@Override
public void drawRect(int x1, int y1, int x2, int y2) {
checkGC();
useStrokeAlpha();
int x = mHScale.translate(x1);
int y = mVScale.translate(y1);
int w = mHScale.scale(x2 - x1);
int h = mVScale.scale(y2 - y1);
getGc().drawRectangle(x, y, w, h);
}
@Override
public void drawRect(@NonNull Point p1, @NonNull Point p2) {
drawRect(p1.x, p1.y, p2.x, p2.y);
}
@Override
public void drawRect(@NonNull Rect r) {
checkGC();
useStrokeAlpha();
int x = mHScale.translate(r.x);
int y = mVScale.translate(r.y);
int w = mHScale.scale(r.w);
int h = mVScale.scale(r.h);
getGc().drawRectangle(x, y, w, h);
}
@Override
public void fillRect(int x1, int y1, int x2, int y2) {
checkGC();
useFillAlpha();
int x = mHScale.translate(x1);
int y = mVScale.translate(y1);
int w = mHScale.scale(x2 - x1);
int h = mVScale.scale(y2 - y1);
getGc().fillRectangle(x, y, w, h);
}
@Override
public void fillRect(@NonNull Point p1, @NonNull Point p2) {
fillRect(p1.x, p1.y, p2.x, p2.y);
}
@Override
public void fillRect(@NonNull Rect r) {
checkGC();
useFillAlpha();
int x = mHScale.translate(r.x);
int y = mVScale.translate(r.y);
int w = mHScale.scale(r.w);
int h = mVScale.scale(r.h);
getGc().fillRectangle(x, y, w, h);
}
// circles (actually ovals)
public void drawOval(int x1, int y1, int x2, int y2) {
checkGC();
useStrokeAlpha();
int x = mHScale.translate(x1);
int y = mVScale.translate(y1);
int w = mHScale.scale(x2 - x1);
int h = mVScale.scale(y2 - y1);
getGc().drawOval(x, y, w, h);
}
public void drawOval(Point p1, Point p2) {
drawOval(p1.x, p1.y, p2.x, p2.y);
}
public void drawOval(Rect r) {
checkGC();
useStrokeAlpha();
int x = mHScale.translate(r.x);
int y = mVScale.translate(r.y);
int w = mHScale.scale(r.w);
int h = mVScale.scale(r.h);
getGc().drawOval(x, y, w, h);
}
public void fillOval(int x1, int y1, int x2, int y2) {
checkGC();
useFillAlpha();
int x = mHScale.translate(x1);
int y = mVScale.translate(y1);
int w = mHScale.scale(x2 - x1);
int h = mVScale.scale(y2 - y1);
getGc().fillOval(x, y, w, h);
}
public void fillOval(Point p1, Point p2) {
fillOval(p1.x, p1.y, p2.x, p2.y);
}
public void fillOval(Rect r) {
checkGC();
useFillAlpha();
int x = mHScale.translate(r.x);
int y = mVScale.translate(r.y);
int w = mHScale.scale(r.w);
int h = mVScale.scale(r.h);
getGc().fillOval(x, y, w, h);
}
// strings
@Override
public void drawString(@NonNull String string, int x, int y) {
checkGC();
useStrokeAlpha();
x = mHScale.translate(x);
y = mVScale.translate(y);
// Background fill of text is not useful because it does not
// use the alpha; we instead supply a separate method (drawBoxedStrings) which
// first paints a semi-transparent mask for the text to sit on
// top of (this ensures that the text is readable regardless of
// colors of the pixels below the text)
getGc().drawString(string, x, y, true /*isTransparent*/);
}
@Override
public void drawBoxedStrings(int x, int y, @NonNull List<?> strings) {
checkGC();
x = mHScale.translate(x);
y = mVScale.translate(y);
// Compute bounds of the box by adding up the sum of the text heights
// and the max of the text widths
int width = 0;
int height = 0;
int lineHeight = getGc().getFontMetrics().getHeight();
for (Object s : strings) {
org.eclipse.swt.graphics.Point extent = getGc().stringExtent(s.toString());
height += extent.y;
width = Math.max(width, extent.x);
}
// Paint a box below the text
int padding = 2;
useFillAlpha();
getGc().fillRectangle(x - padding, y - padding, width + 2 * padding, height + 2 * padding);
// Finally draw strings on top
useStrokeAlpha();
int lineY = y;
for (Object s : strings) {
getGc().drawString(s.toString(), x, lineY, true /* isTransparent */);
lineY += lineHeight;
}
}
@Override
public void drawString(@NonNull String string, @NonNull Point topLeft) {
drawString(string, topLeft.x, topLeft.y);
}
// Styles
@Override
public void useStyle(@NonNull DrawingStyle style) {
checkGC();
// Look up the specific SWT style which defines the actual
// colors and attributes to be used for the logical drawing style.
SwtDrawingStyle swtStyle = SwtDrawingStyle.of(style);
RGB stroke = swtStyle.getStrokeColor();
if (stroke != null) {
Color color = getStrokeColor(style, stroke);
mGc.setForeground(color);
}
RGB fill = swtStyle.getFillColor();
if (fill != null) {
Color color = getFillColor(style, fill);
mGc.setBackground(color);
}
mGc.setLineWidth(swtStyle.getLineWidth());
mGc.setLineStyle(swtStyle.getLineStyle());
if (swtStyle.getLineStyle() == SWT.LINE_CUSTOM) {
mGc.setLineDash(new int[] {
8, 4
});
}
mCurrentStyle = swtStyle;
}
/** Uses the stroke alpha for subsequent drawing operations. */
private void useStrokeAlpha() {
mGc.setAlpha(mCurrentStyle.getStrokeAlpha());
}
/** Uses the fill alpha for subsequent drawing operations. */
private void useFillAlpha() {
mGc.setAlpha(mCurrentStyle.getFillAlpha());
}
/**
* Get the SWT stroke color (foreground/border) to use for the given style,
* using the provided color description if we haven't seen this color yet.
* The color will also be placed in the {@link #mStyleStrokeMap} such that
* it can be disposed of at cleanup time.
*
* @param style The drawing style for which we want a color
* @param defaultColorDesc The RGB values to initialize the color to if we
* haven't seen this color before
* @return The color object
*/
private Color getStrokeColor(DrawingStyle style, RGB defaultColorDesc) {
return getStyleColor(style, defaultColorDesc, mStyleStrokeMap);
}
/**
* Get the SWT fill (background/interior) color to use for the given style,
* using the provided color description if we haven't seen this color yet.
* The color will also be placed in the {@link #mStyleStrokeMap} such that
* it can be disposed of at cleanup time.
*
* @param style The drawing style for which we want a color
* @param defaultColorDesc The RGB values to initialize the color to if we
* haven't seen this color before
* @return The color object
*/
private Color getFillColor(DrawingStyle style, RGB defaultColorDesc) {
return getStyleColor(style, defaultColorDesc, mStyleFillMap);
}
/**
* Get the SWT color to use for the given style, using the provided color
* description if we haven't seen this color yet. The color will also be
* placed in the map referenced by the map parameter such that it can be
* disposed of at cleanup time.
*
* @param style The drawing style for which we want a color
* @param defaultColorDesc The RGB values to initialize the color to if we
* haven't seen this color before
* @param map The color map to use
* @return The color object
*/
private Color getStyleColor(DrawingStyle style, RGB defaultColorDesc,
Map<DrawingStyle, Color> map) {
Color color = map.get(style);
if (color == null) {
color = new Color(getGc().getDevice(), defaultColorDesc);
map.put(style, color);
}
return color;
}
// dots
@Override
public void drawPoint(int x, int y) {
checkGC();
useStrokeAlpha();
x = mHScale.translate(x);
y = mVScale.translate(y);
getGc().drawPoint(x, y);
}
// arrows
private static final int MIN_LENGTH = 10;
@Override
public void drawArrow(int x1, int y1, int x2, int y2, int size) {
int arrowWidth = size;
int arrowHeight = size;
checkGC();
useStrokeAlpha();
x1 = mHScale.translate(x1);
y1 = mVScale.translate(y1);
x2 = mHScale.translate(x2);
y2 = mVScale.translate(y2);
GC graphics = getGc();
// Make size adjustments to ensure that the arrow has enough width to be visible
if (x1 == x2 && Math.abs(y1 - y2) < MIN_LENGTH) {
int delta = (MIN_LENGTH - Math.abs(y1 - y2)) / 2;
if (y1 < y2) {
y1 -= delta;
y2 += delta;
} else {
y1 += delta;
y2-= delta;
}
} else if (y1 == y2 && Math.abs(x1 - x2) < MIN_LENGTH) {
int delta = (MIN_LENGTH - Math.abs(x1 - x2)) / 2;
if (x1 < x2) {
x1 -= delta;
x2 += delta;
} else {
x1 += delta;
x2-= delta;
}
}
graphics.drawLine(x1, y1, x2, y2);
// Arrowhead:
if (x1 == x2) {
// Vertical
if (y2 > y1) {
graphics.drawLine(x2 - arrowWidth, y2 - arrowHeight, x2, y2);
graphics.drawLine(x2 + arrowWidth, y2 - arrowHeight, x2, y2);
} else {
graphics.drawLine(x2 - arrowWidth, y2 + arrowHeight, x2, y2);
graphics.drawLine(x2 + arrowWidth, y2 + arrowHeight, x2, y2);
}
} else if (y1 == y2) {
// Horizontal
if (x2 > x1) {
graphics.drawLine(x2 - arrowHeight, y2 - arrowWidth, x2, y2);
graphics.drawLine(x2 - arrowHeight, y2 + arrowWidth, x2, y2);
} else {
graphics.drawLine(x2 + arrowHeight, y2 - arrowWidth, x2, y2);
graphics.drawLine(x2 + arrowHeight, y2 + arrowWidth, x2, y2);
}
} else {
// Compute angle:
int dy = y2 - y1;
int dx = x2 - x1;
double angle = Math.atan2(dy, dx);
double lineLength = Math.sqrt(dy * dy + dx * dx);
// Imagine a line of the same length as the arrow, but with angle 0.
// Its two arrow lines are at (-arrowWidth, -arrowHeight) relative
// to the endpoint (x1 + lineLength, y1) stretching up to (x2,y2).
// We compute the positions of (ax,ay) for the point above and
// below this line and paint the lines to it:
double ax = x1 + lineLength - arrowHeight;
double ay = y1 - arrowWidth;
int rx = (int) (Math.cos(angle) * (ax-x1) - Math.sin(angle) * (ay-y1) + x1);
int ry = (int) (Math.sin(angle) * (ax-x1) + Math.cos(angle) * (ay-y1) + y1);
graphics.drawLine(x2, y2, rx, ry);
ay = y1 + arrowWidth;
rx = (int) (Math.cos(angle) * (ax-x1) - Math.sin(angle) * (ay-y1) + x1);
ry = (int) (Math.sin(angle) * (ax-x1) + Math.cos(angle) * (ay-y1) + y1);
graphics.drawLine(x2, y2, rx, ry);
}
/* TODO: Experiment with filled arrow heads?
if (x1 == x2) {
// Vertical
if (y2 > y1) {
for (int i = 0; i < arrowWidth; i++) {
graphics.drawLine(x2 - arrowWidth + i, y2 - arrowWidth + i,
x2 + arrowWidth - i, y2 - arrowWidth + i);
}
} else {
for (int i = 0; i < arrowWidth; i++) {
graphics.drawLine(x2 - arrowWidth + i, y2 + arrowWidth - i,
x2 + arrowWidth - i, y2 + arrowWidth - i);
}
}
} else if (y1 == y2) {
// Horizontal
if (x2 > x1) {
for (int i = 0; i < arrowHeight; i++) {
graphics.drawLine(x2 - arrowHeight + i, y2 - arrowHeight + i, x2
- arrowHeight + i, y2 + arrowHeight - i);
}
} else {
for (int i = 0; i < arrowHeight; i++) {
graphics.drawLine(x2 + arrowHeight - i, y2 - arrowHeight + i, x2
+ arrowHeight - i, y2 + arrowHeight - i);
}
}
} else {
// Arbitrary angle -- need to use trig
// TODO: Implement this
}
*/
}
}