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