/*
* 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.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 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();
}
//-------------
public 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. */
public int getFontHeight() {
if (mFontHeight < 1) {
checkGC();
FontMetrics fm = getGc().getFontMetrics();
mFontHeight = fm.getHeight();
}
return mFontHeight;
}
public IColor getForeground() {
Color c = getGc().getForeground();
return new ColorWrapper(c);
}
public IColor getBackground() {
Color c = getGc().getBackground();
return new ColorWrapper(c);
}
public int getAlpha() {
return getGc().getAlpha();
}
public void setForeground(IColor color) {
checkGC();
getGc().setForeground(((ColorWrapper) color).getColor());
}
public void setBackground(IColor color) {
checkGC();
getGc().setBackground(((ColorWrapper) color).getColor());
}
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.
}
}
public void setLineStyle(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);
}
}
public void setLineWidth(int width) {
checkGC();
if (width > 0) {
getGc().setLineWidth(width);
}
}
// lines
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);
}
public void drawLine(Point p1, Point p2) {
drawLine(p1.x, p1.y, p2.x, p2.y);
}
// rectangles
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);
}
public void drawRect(Point p1, Point p2) {
drawRect(p1.x, p1.y, p2.x, p2.y);
}
public void drawRect(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);
}
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);
}
public void fillRect(Point p1, Point p2) {
fillRect(p1.x, p1.y, p2.x, p2.y);
}
public void fillRect(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
public void drawString(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*/);
}
public void drawBoxedStrings(int x, int y, 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;
}
}
public void drawString(String string, Point topLeft) {
drawString(string, topLeft.x, topLeft.y);
}
// Styles
public void useStyle(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;
}
}