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 
ColorWrapper(Color color)65         public ColorWrapper(Color color) {
66             mColor = color;
67         }
68 
getColor()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 
GCWrapper(CanvasTransform hScale, CanvasTransform vScale)99     public GCWrapper(CanvasTransform hScale, CanvasTransform vScale) {
100         mHScale = hScale;
101         mVScale = vScale;
102         mGc = null;
103     }
104 
setGC(GC gc)105     void setGC(GC gc) {
106         mGc = gc;
107     }
108 
getGc()109     private GC getGc() {
110         return mGc;
111     }
112 
checkGC()113     void checkGC() {
114         if (mGc == null) {
115             throw new RuntimeException("IGraphics used without a valid context.");
116         }
117     }
118 
dispose()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
registerColor(int rgb)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
getFontHeight()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
getForeground()167     public @NonNull IColor getForeground() {
168         Color c = getGc().getForeground();
169         return new ColorWrapper(c);
170     }
171 
172     @Override
getBackground()173     public @NonNull IColor getBackground() {
174         Color c = getGc().getBackground();
175         return new ColorWrapper(c);
176     }
177 
178     @Override
getAlpha()179     public int getAlpha() {
180         return getGc().getAlpha();
181     }
182 
183     @Override
setForeground(@onNull IColor color)184     public void setForeground(@NonNull IColor color) {
185         checkGC();
186         getGc().setForeground(((ColorWrapper) color).getColor());
187     }
188 
189     @Override
setBackground(@onNull IColor color)190     public void setBackground(@NonNull IColor color) {
191         checkGC();
192         getGc().setBackground(((ColorWrapper) color).getColor());
193     }
194 
195     @Override
setAlpha(int alpha)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
setLineStyle(@onNull LineStyle style)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
setLineWidth(int width)237     public void setLineWidth(int width) {
238         checkGC();
239         if (width > 0) {
240             getGc().setLineWidth(width);
241         }
242     }
243 
244     // lines
245 
246     @Override
drawLine(int x1, int y1, int x2, int y2)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
drawLine(@onNull Point p1, @NonNull Point p2)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
drawRect(int x1, int y1, int x2, int y2)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
drawRect(@onNull Point p1, @NonNull Point p2)276     public void drawRect(@NonNull Point p1, @NonNull Point p2) {
277         drawRect(p1.x, p1.y, p2.x, p2.y);
278     }
279 
280     @Override
drawRect(@onNull Rect r)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
fillRect(int x1, int y1, int x2, int y2)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
fillRect(@onNull Point p1, @NonNull Point p2)303     public void fillRect(@NonNull Point p1, @NonNull Point p2) {
304         fillRect(p1.x, p1.y, p2.x, p2.y);
305     }
306 
307     @Override
fillRect(@onNull Rect r)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 
drawOval(int x1, int y1, int x2, int y2)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 
drawOval(Point p1, Point p2)330     public void drawOval(Point p1, Point p2) {
331         drawOval(p1.x, p1.y, p2.x, p2.y);
332     }
333 
drawOval(Rect r)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 
fillOval(int x1, int y1, int x2, int y2)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 
fillOval(Point p1, Point p2)354     public void fillOval(Point p1, Point p2) {
355         fillOval(p1.x, p1.y, p2.x, p2.y);
356     }
357 
fillOval(Rect r)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
drawString(@onNull String string, int x, int y)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
drawBoxedStrings(int x, int y, @NonNull List<?> strings)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
drawString(@onNull String string, @NonNull Point topLeft)418     public void drawString(@NonNull String string, @NonNull Point topLeft) {
419         drawString(string, topLeft.x, topLeft.y);
420     }
421 
422     // Styles
423 
424     @Override
useStyle(@onNull DrawingStyle style)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. */
useStrokeAlpha()452     private void useStrokeAlpha() {
453         mGc.setAlpha(mCurrentStyle.getStrokeAlpha());
454     }
455 
456     /** Uses the fill alpha for subsequent drawing operations. */
useFillAlpha()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      */
getStrokeColor(DrawingStyle style, RGB defaultColorDesc)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      */
getFillColor(DrawingStyle style, RGB defaultColorDesc)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      */
getStyleColor(DrawingStyle style, RGB defaultColorDesc, Map<DrawingStyle, Color> map)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
drawPoint(int x, int y)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
drawArrow(int x1, int y1, int x2, int y2, int size)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