aboutsummaryrefslogtreecommitdiff
path: root/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GCWrapper.java
diff options
context:
space:
mode:
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GCWrapper.java')
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GCWrapper.java645
1 files changed, 645 insertions, 0 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GCWrapper.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GCWrapper.java
new file mode 100644
index 000000000..354517e76
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GCWrapper.java
@@ -0,0 +1,645 @@
+/*
+ * 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
+ }
+ */
+ }
+}