Home | History | Annotate | Download | only in gle2
      1 /*
      2  * Copyright (C) 2010 The Android Open Source Project
      3  *
      4  * Licensed under the Eclipse Public License, Version 1.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.eclipse.org/org/documents/epl-v10.php
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.ide.eclipse.adt.internal.editors.layout.gle2;
     18 
     19 import com.android.annotations.NonNull;
     20 import com.android.ide.common.api.DrawingStyle;
     21 import com.android.ide.common.api.IColor;
     22 import com.android.ide.common.api.IGraphics;
     23 import com.android.ide.common.api.IViewRule;
     24 import com.android.ide.common.api.Point;
     25 import com.android.ide.common.api.Rect;
     26 
     27 import org.eclipse.swt.SWT;
     28 import org.eclipse.swt.SWTException;
     29 import org.eclipse.swt.graphics.Color;
     30 import org.eclipse.swt.graphics.FontMetrics;
     31 import org.eclipse.swt.graphics.GC;
     32 import org.eclipse.swt.graphics.RGB;
     33 
     34 import java.util.EnumMap;
     35 import java.util.HashMap;
     36 import java.util.List;
     37 import java.util.Map;
     38 
     39 /**
     40  * Wraps an SWT {@link GC} into an {@link IGraphics} interface so that {@link IViewRule} objects
     41  * can directly draw on the canvas.
     42  * <p/>
     43  * The actual wrapped GC object is only non-null during the context of a paint operation.
     44  */
     45 public class GCWrapper implements IGraphics {
     46 
     47     /**
     48      * The actual SWT {@link GC} being wrapped. This can change during the lifetime of the
     49      * object. It is generally set to something during an onPaint method and then changed
     50      * to null when not in the context of a paint.
     51      */
     52     private GC mGc;
     53 
     54     /**
     55      * Current style being used for drawing.
     56      */
     57     private SwtDrawingStyle mCurrentStyle = SwtDrawingStyle.INVALID;
     58 
     59     /**
     60      * Implementation of IColor wrapping an SWT color.
     61      */
     62     private static class ColorWrapper implements IColor {
     63         private final Color mColor;
     64 
     65         public ColorWrapper(Color color) {
     66             mColor = color;
     67         }
     68 
     69         public Color getColor() {
     70             return mColor;
     71         }
     72     }
     73 
     74     /** A map of registered colors. All these colors must be disposed at the end. */
     75     private final HashMap<Integer, ColorWrapper> mColorMap = new HashMap<Integer, ColorWrapper>();
     76 
     77     /**
     78      * A map of the {@link SwtDrawingStyle} stroke colors that we have actually
     79      * used (to be disposed)
     80      */
     81     private final Map<DrawingStyle, Color> mStyleStrokeMap = new EnumMap<DrawingStyle, Color>(
     82             DrawingStyle.class);
     83 
     84     /**
     85      * A map of the {@link SwtDrawingStyle} fill colors that we have actually
     86      * used (to be disposed)
     87      */
     88     private final Map<DrawingStyle, Color> mStyleFillMap = new EnumMap<DrawingStyle, Color>(
     89             DrawingStyle.class);
     90 
     91     /** The cached pixel height of the default current font. */
     92     private int mFontHeight = 0;
     93 
     94     /** The scaling of the canvas in X. */
     95     private final CanvasTransform mHScale;
     96     /** The scaling of the canvas in Y. */
     97     private final CanvasTransform mVScale;
     98 
     99     public GCWrapper(CanvasTransform hScale, CanvasTransform vScale) {
    100         mHScale = hScale;
    101         mVScale = vScale;
    102         mGc = null;
    103     }
    104 
    105     void setGC(GC gc) {
    106         mGc = gc;
    107     }
    108 
    109     private GC getGc() {
    110         return mGc;
    111     }
    112 
    113     void checkGC() {
    114         if (mGc == null) {
    115             throw new RuntimeException("IGraphics used without a valid context.");
    116         }
    117     }
    118 
    119     void dispose() {
    120         for (ColorWrapper c : mColorMap.values()) {
    121             c.getColor().dispose();
    122         }
    123         mColorMap.clear();
    124 
    125         for (Color c : mStyleStrokeMap.values()) {
    126             c.dispose();
    127         }
    128         mStyleStrokeMap.clear();
    129 
    130         for (Color c : mStyleFillMap.values()) {
    131             c.dispose();
    132         }
    133         mStyleFillMap.clear();
    134     }
    135 
    136     //-------------
    137 
    138     @Override
    139     public @NonNull IColor registerColor(int rgb) {
    140         checkGC();
    141 
    142         Integer key = Integer.valueOf(rgb);
    143         ColorWrapper c = mColorMap.get(key);
    144         if (c == null) {
    145             c = new ColorWrapper(new Color(getGc().getDevice(),
    146                     (rgb >> 16) & 0xFF,
    147                     (rgb >>  8) & 0xFF,
    148                     (rgb >>  0) & 0xFF));
    149             mColorMap.put(key, c);
    150         }
    151 
    152         return c;
    153     }
    154 
    155     /** Returns the (cached) pixel height of the current font. */
    156     @Override
    157     public int getFontHeight() {
    158         if (mFontHeight < 1) {
    159             checkGC();
    160             FontMetrics fm = getGc().getFontMetrics();
    161             mFontHeight = fm.getHeight();
    162         }
    163         return mFontHeight;
    164     }
    165 
    166     @Override
    167     public @NonNull IColor getForeground() {
    168         Color c = getGc().getForeground();
    169         return new ColorWrapper(c);
    170     }
    171 
    172     @Override
    173     public @NonNull IColor getBackground() {
    174         Color c = getGc().getBackground();
    175         return new ColorWrapper(c);
    176     }
    177 
    178     @Override
    179     public int getAlpha() {
    180         return getGc().getAlpha();
    181     }
    182 
    183     @Override
    184     public void setForeground(@NonNull IColor color) {
    185         checkGC();
    186         getGc().setForeground(((ColorWrapper) color).getColor());
    187     }
    188 
    189     @Override
    190     public void setBackground(@NonNull IColor color) {
    191         checkGC();
    192         getGc().setBackground(((ColorWrapper) color).getColor());
    193     }
    194 
    195     @Override
    196     public void setAlpha(int alpha) {
    197         checkGC();
    198         try {
    199             getGc().setAlpha(alpha);
    200         } catch (SWTException e) {
    201             // This means that we cannot set the alpha on this platform; this is
    202             // an acceptable no-op.
    203         }
    204     }
    205 
    206     @Override
    207     public void setLineStyle(@NonNull LineStyle style) {
    208         int swtStyle = 0;
    209         switch (style) {
    210         case LINE_SOLID:
    211             swtStyle = SWT.LINE_SOLID;
    212             break;
    213         case LINE_DASH:
    214             swtStyle = SWT.LINE_DASH;
    215             break;
    216         case LINE_DOT:
    217             swtStyle = SWT.LINE_DOT;
    218             break;
    219         case LINE_DASHDOT:
    220             swtStyle = SWT.LINE_DASHDOT;
    221             break;
    222         case LINE_DASHDOTDOT:
    223             swtStyle = SWT.LINE_DASHDOTDOT;
    224             break;
    225         default:
    226             assert false : style;
    227             break;
    228         }
    229 
    230         if (swtStyle != 0) {
    231             checkGC();
    232             getGc().setLineStyle(swtStyle);
    233         }
    234     }
    235 
    236     @Override
    237     public void setLineWidth(int width) {
    238         checkGC();
    239         if (width > 0) {
    240             getGc().setLineWidth(width);
    241         }
    242     }
    243 
    244     // lines
    245 
    246     @Override
    247     public void drawLine(int x1, int y1, int x2, int y2) {
    248         checkGC();
    249         useStrokeAlpha();
    250         x1 = mHScale.translate(x1);
    251         y1 = mVScale.translate(y1);
    252         x2 = mHScale.translate(x2);
    253         y2 = mVScale.translate(y2);
    254         getGc().drawLine(x1, y1, x2, y2);
    255     }
    256 
    257     @Override
    258     public void drawLine(@NonNull Point p1, @NonNull Point p2) {
    259         drawLine(p1.x, p1.y, p2.x, p2.y);
    260     }
    261 
    262     // rectangles
    263 
    264     @Override
    265     public void drawRect(int x1, int y1, int x2, int y2) {
    266         checkGC();
    267         useStrokeAlpha();
    268         int x = mHScale.translate(x1);
    269         int y = mVScale.translate(y1);
    270         int w = mHScale.scale(x2 - x1);
    271         int h = mVScale.scale(y2 - y1);
    272         getGc().drawRectangle(x, y, w, h);
    273     }
    274 
    275     @Override
    276     public void drawRect(@NonNull Point p1, @NonNull Point p2) {
    277         drawRect(p1.x, p1.y, p2.x, p2.y);
    278     }
    279 
    280     @Override
    281     public void drawRect(@NonNull Rect r) {
    282         checkGC();
    283         useStrokeAlpha();
    284         int x = mHScale.translate(r.x);
    285         int y = mVScale.translate(r.y);
    286         int w = mHScale.scale(r.w);
    287         int h = mVScale.scale(r.h);
    288         getGc().drawRectangle(x, y, w, h);
    289     }
    290 
    291     @Override
    292     public void fillRect(int x1, int y1, int x2, int y2) {
    293         checkGC();
    294         useFillAlpha();
    295         int x = mHScale.translate(x1);
    296         int y = mVScale.translate(y1);
    297         int w = mHScale.scale(x2 - x1);
    298         int h = mVScale.scale(y2 - y1);
    299         getGc().fillRectangle(x, y, w, h);
    300     }
    301 
    302     @Override
    303     public void fillRect(@NonNull Point p1, @NonNull Point p2) {
    304         fillRect(p1.x, p1.y, p2.x, p2.y);
    305     }
    306 
    307     @Override
    308     public void fillRect(@NonNull Rect r) {
    309         checkGC();
    310         useFillAlpha();
    311         int x = mHScale.translate(r.x);
    312         int y = mVScale.translate(r.y);
    313         int w = mHScale.scale(r.w);
    314         int h = mVScale.scale(r.h);
    315         getGc().fillRectangle(x, y, w, h);
    316     }
    317 
    318     // circles (actually ovals)
    319 
    320     public void drawOval(int x1, int y1, int x2, int y2) {
    321         checkGC();
    322         useStrokeAlpha();
    323         int x = mHScale.translate(x1);
    324         int y = mVScale.translate(y1);
    325         int w = mHScale.scale(x2 - x1);
    326         int h = mVScale.scale(y2 - y1);
    327         getGc().drawOval(x, y, w, h);
    328     }
    329 
    330     public void drawOval(Point p1, Point p2) {
    331         drawOval(p1.x, p1.y, p2.x, p2.y);
    332     }
    333 
    334     public void drawOval(Rect r) {
    335         checkGC();
    336         useStrokeAlpha();
    337         int x = mHScale.translate(r.x);
    338         int y = mVScale.translate(r.y);
    339         int w = mHScale.scale(r.w);
    340         int h = mVScale.scale(r.h);
    341         getGc().drawOval(x, y, w, h);
    342     }
    343 
    344     public void fillOval(int x1, int y1, int x2, int y2) {
    345         checkGC();
    346         useFillAlpha();
    347         int x = mHScale.translate(x1);
    348         int y = mVScale.translate(y1);
    349         int w = mHScale.scale(x2 - x1);
    350         int h = mVScale.scale(y2 - y1);
    351         getGc().fillOval(x, y, w, h);
    352     }
    353 
    354     public void fillOval(Point p1, Point p2) {
    355         fillOval(p1.x, p1.y, p2.x, p2.y);
    356     }
    357 
    358     public void fillOval(Rect r) {
    359         checkGC();
    360         useFillAlpha();
    361         int x = mHScale.translate(r.x);
    362         int y = mVScale.translate(r.y);
    363         int w = mHScale.scale(r.w);
    364         int h = mVScale.scale(r.h);
    365         getGc().fillOval(x, y, w, h);
    366     }
    367 
    368 
    369     // strings
    370 
    371     @Override
    372     public void drawString(@NonNull String string, int x, int y) {
    373         checkGC();
    374         useStrokeAlpha();
    375         x = mHScale.translate(x);
    376         y = mVScale.translate(y);
    377         // Background fill of text is not useful because it does not
    378         // use the alpha; we instead supply a separate method (drawBoxedStrings) which
    379         // first paints a semi-transparent mask for the text to sit on
    380         // top of (this ensures that the text is readable regardless of
    381         // colors of the pixels below the text)
    382         getGc().drawString(string, x, y, true /*isTransparent*/);
    383     }
    384 
    385     @Override
    386     public void drawBoxedStrings(int x, int y, @NonNull List<?> strings) {
    387         checkGC();
    388 
    389         x = mHScale.translate(x);
    390         y = mVScale.translate(y);
    391 
    392         // Compute bounds of the box by adding up the sum of the text heights
    393         // and the max of the text widths
    394         int width = 0;
    395         int height = 0;
    396         int lineHeight = getGc().getFontMetrics().getHeight();
    397         for (Object s : strings) {
    398             org.eclipse.swt.graphics.Point extent = getGc().stringExtent(s.toString());
    399             height += extent.y;
    400             width = Math.max(width, extent.x);
    401         }
    402 
    403         // Paint a box below the text
    404         int padding = 2;
    405         useFillAlpha();
    406         getGc().fillRectangle(x - padding, y - padding, width + 2 * padding, height + 2 * padding);
    407 
    408         // Finally draw strings on top
    409         useStrokeAlpha();
    410         int lineY = y;
    411         for (Object s : strings) {
    412             getGc().drawString(s.toString(), x, lineY, true /* isTransparent */);
    413             lineY += lineHeight;
    414         }
    415     }
    416 
    417     @Override
    418     public void drawString(@NonNull String string, @NonNull Point topLeft) {
    419         drawString(string, topLeft.x, topLeft.y);
    420     }
    421 
    422     // Styles
    423 
    424     @Override
    425     public void useStyle(@NonNull DrawingStyle style) {
    426         checkGC();
    427 
    428         // Look up the specific SWT style which defines the actual
    429         // colors and attributes to be used for the logical drawing style.
    430         SwtDrawingStyle swtStyle = SwtDrawingStyle.of(style);
    431         RGB stroke = swtStyle.getStrokeColor();
    432         if (stroke != null) {
    433             Color color = getStrokeColor(style, stroke);
    434             mGc.setForeground(color);
    435         }
    436         RGB fill = swtStyle.getFillColor();
    437         if (fill != null) {
    438             Color color = getFillColor(style, fill);
    439             mGc.setBackground(color);
    440         }
    441         mGc.setLineWidth(swtStyle.getLineWidth());
    442         mGc.setLineStyle(swtStyle.getLineStyle());
    443         if (swtStyle.getLineStyle() == SWT.LINE_CUSTOM) {
    444             mGc.setLineDash(new int[] {
    445                     8, 4
    446             });
    447         }
    448         mCurrentStyle = swtStyle;
    449     }
    450 
    451     /** Uses the stroke alpha for subsequent drawing operations. */
    452     private void useStrokeAlpha() {
    453         mGc.setAlpha(mCurrentStyle.getStrokeAlpha());
    454     }
    455 
    456     /** Uses the fill alpha for subsequent drawing operations. */
    457     private void useFillAlpha() {
    458         mGc.setAlpha(mCurrentStyle.getFillAlpha());
    459     }
    460 
    461     /**
    462      * Get the SWT stroke color (foreground/border) to use for the given style,
    463      * using the provided color description if we haven't seen this color yet.
    464      * The color will also be placed in the {@link #mStyleStrokeMap} such that
    465      * it can be disposed of at cleanup time.
    466      *
    467      * @param style The drawing style for which we want a color
    468      * @param defaultColorDesc The RGB values to initialize the color to if we
    469      *            haven't seen this color before
    470      * @return The color object
    471      */
    472     private Color getStrokeColor(DrawingStyle style, RGB defaultColorDesc) {
    473         return getStyleColor(style, defaultColorDesc, mStyleStrokeMap);
    474     }
    475 
    476     /**
    477      * Get the SWT fill (background/interior) color to use for the given style,
    478      * using the provided color description if we haven't seen this color yet.
    479      * The color will also be placed in the {@link #mStyleStrokeMap} such that
    480      * it can be disposed of at cleanup time.
    481      *
    482      * @param style The drawing style for which we want a color
    483      * @param defaultColorDesc The RGB values to initialize the color to if we
    484      *            haven't seen this color before
    485      * @return The color object
    486      */
    487     private Color getFillColor(DrawingStyle style, RGB defaultColorDesc) {
    488         return getStyleColor(style, defaultColorDesc, mStyleFillMap);
    489     }
    490 
    491     /**
    492      * Get the SWT color to use for the given style, using the provided color
    493      * description if we haven't seen this color yet. The color will also be
    494      * placed in the map referenced by the map parameter such that it can be
    495      * disposed of at cleanup time.
    496      *
    497      * @param style The drawing style for which we want a color
    498      * @param defaultColorDesc The RGB values to initialize the color to if we
    499      *            haven't seen this color before
    500      * @param map The color map to use
    501      * @return The color object
    502      */
    503     private Color getStyleColor(DrawingStyle style, RGB defaultColorDesc,
    504             Map<DrawingStyle, Color> map) {
    505         Color color = map.get(style);
    506         if (color == null) {
    507             color = new Color(getGc().getDevice(), defaultColorDesc);
    508             map.put(style, color);
    509         }
    510 
    511         return color;
    512     }
    513 
    514     // dots
    515 
    516     @Override
    517     public void drawPoint(int x, int y) {
    518         checkGC();
    519         useStrokeAlpha();
    520         x = mHScale.translate(x);
    521         y = mVScale.translate(y);
    522 
    523         getGc().drawPoint(x, y);
    524     }
    525 
    526     // arrows
    527 
    528     private static final int MIN_LENGTH = 10;
    529 
    530 
    531     @Override
    532     public void drawArrow(int x1, int y1, int x2, int y2, int size) {
    533         int arrowWidth = size;
    534         int arrowHeight = size;
    535 
    536         checkGC();
    537         useStrokeAlpha();
    538         x1 = mHScale.translate(x1);
    539         y1 = mVScale.translate(y1);
    540         x2 = mHScale.translate(x2);
    541         y2 = mVScale.translate(y2);
    542         GC graphics = getGc();
    543 
    544         // Make size adjustments to ensure that the arrow has enough width to be visible
    545         if (x1 == x2 && Math.abs(y1 - y2) < MIN_LENGTH) {
    546             int delta = (MIN_LENGTH - Math.abs(y1 - y2)) / 2;
    547             if (y1 < y2) {
    548                 y1 -= delta;
    549                 y2 += delta;
    550             } else {
    551                 y1 += delta;
    552                 y2-= delta;
    553             }
    554 
    555         } else if (y1 == y2 && Math.abs(x1 - x2) < MIN_LENGTH) {
    556             int delta = (MIN_LENGTH - Math.abs(x1 - x2)) / 2;
    557             if (x1 < x2) {
    558                 x1 -= delta;
    559                 x2 += delta;
    560             } else {
    561                 x1 += delta;
    562                 x2-= delta;
    563             }
    564         }
    565 
    566         graphics.drawLine(x1, y1, x2, y2);
    567 
    568         // Arrowhead:
    569 
    570         if (x1 == x2) {
    571             // Vertical
    572             if (y2 > y1) {
    573                 graphics.drawLine(x2 - arrowWidth, y2 - arrowHeight, x2, y2);
    574                 graphics.drawLine(x2 + arrowWidth, y2 - arrowHeight, x2, y2);
    575             } else {
    576                 graphics.drawLine(x2 - arrowWidth, y2 + arrowHeight, x2, y2);
    577                 graphics.drawLine(x2 + arrowWidth, y2 + arrowHeight, x2, y2);
    578             }
    579         } else if (y1 == y2) {
    580             // Horizontal
    581             if (x2 > x1) {
    582                 graphics.drawLine(x2 - arrowHeight, y2 - arrowWidth, x2, y2);
    583                 graphics.drawLine(x2 - arrowHeight, y2 + arrowWidth, x2, y2);
    584             } else {
    585                 graphics.drawLine(x2 + arrowHeight, y2 - arrowWidth, x2, y2);
    586                 graphics.drawLine(x2 + arrowHeight, y2 + arrowWidth, x2, y2);
    587             }
    588         } else {
    589             // Compute angle:
    590             int dy = y2 - y1;
    591             int dx = x2 - x1;
    592             double angle = Math.atan2(dy, dx);
    593             double lineLength = Math.sqrt(dy * dy + dx * dx);
    594 
    595             // Imagine a line of the same length as the arrow, but with angle 0.
    596             // Its two arrow lines are at (-arrowWidth, -arrowHeight) relative
    597             // to the endpoint (x1 + lineLength, y1) stretching up to (x2,y2).
    598             // We compute the positions of (ax,ay) for the point above and
    599             // below this line and paint the lines to it:
    600             double ax = x1 + lineLength - arrowHeight;
    601             double ay = y1 - arrowWidth;
    602             int rx = (int) (Math.cos(angle) * (ax-x1) - Math.sin(angle) * (ay-y1) + x1);
    603             int ry = (int) (Math.sin(angle) * (ax-x1) + Math.cos(angle) * (ay-y1) + y1);
    604             graphics.drawLine(x2, y2, rx, ry);
    605 
    606             ay = y1 + arrowWidth;
    607             rx = (int) (Math.cos(angle) * (ax-x1) - Math.sin(angle) * (ay-y1) + x1);
    608             ry = (int) (Math.sin(angle) * (ax-x1) + Math.cos(angle) * (ay-y1) + y1);
    609             graphics.drawLine(x2, y2, rx, ry);
    610         }
    611 
    612         /* TODO: Experiment with filled arrow heads?
    613         if (x1 == x2) {
    614             // Vertical
    615             if (y2 > y1) {
    616                 for (int i = 0; i < arrowWidth; i++) {
    617                     graphics.drawLine(x2 - arrowWidth + i, y2 - arrowWidth + i,
    618                             x2 + arrowWidth - i, y2 - arrowWidth + i);
    619                 }
    620             } else {
    621                 for (int i = 0; i < arrowWidth; i++) {
    622                     graphics.drawLine(x2 - arrowWidth + i, y2 + arrowWidth - i,
    623                             x2 + arrowWidth - i, y2 + arrowWidth - i);
    624                 }
    625             }
    626         } else if (y1 == y2) {
    627             // Horizontal
    628             if (x2 > x1) {
    629                 for (int i = 0; i < arrowHeight; i++) {
    630                     graphics.drawLine(x2 - arrowHeight + i, y2 - arrowHeight + i, x2
    631                             - arrowHeight + i, y2 + arrowHeight - i);
    632                 }
    633             } else {
    634                 for (int i = 0; i < arrowHeight; i++) {
    635                     graphics.drawLine(x2 + arrowHeight - i, y2 - arrowHeight + i, x2
    636                             + arrowHeight - i, y2 + arrowHeight - i);
    637                 }
    638             }
    639         } else {
    640             // Arbitrary angle -- need to use trig
    641             // TODO: Implement this
    642         }
    643         */
    644     }
    645 }
    646