• 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 
registerColor(int rgb)137     public IColor registerColor(int rgb) {
138         checkGC();
139 
140         Integer key = Integer.valueOf(rgb);
141         ColorWrapper c = mColorMap.get(key);
142         if (c == null) {
143             c = new ColorWrapper(new Color(getGc().getDevice(),
144                     (rgb >> 16) & 0xFF,
145                     (rgb >>  8) & 0xFF,
146                     (rgb >>  0) & 0xFF));
147             mColorMap.put(key, c);
148         }
149 
150         return c;
151     }
152 
153     /** Returns the (cached) pixel height of the current font. */
getFontHeight()154     public int getFontHeight() {
155         if (mFontHeight < 1) {
156             checkGC();
157             FontMetrics fm = getGc().getFontMetrics();
158             mFontHeight = fm.getHeight();
159         }
160         return mFontHeight;
161     }
162 
getForeground()163     public IColor getForeground() {
164         Color c = getGc().getForeground();
165         return new ColorWrapper(c);
166     }
167 
getBackground()168     public IColor getBackground() {
169         Color c = getGc().getBackground();
170         return new ColorWrapper(c);
171     }
172 
getAlpha()173     public int getAlpha() {
174         return getGc().getAlpha();
175     }
176 
setForeground(IColor color)177     public void setForeground(IColor color) {
178         checkGC();
179         getGc().setForeground(((ColorWrapper) color).getColor());
180     }
181 
setBackground(IColor color)182     public void setBackground(IColor color) {
183         checkGC();
184         getGc().setBackground(((ColorWrapper) color).getColor());
185     }
186 
setAlpha(int alpha)187     public void setAlpha(int alpha) {
188         checkGC();
189         try {
190             getGc().setAlpha(alpha);
191         } catch (SWTException e) {
192             // This means that we cannot set the alpha on this platform; this is
193             // an acceptable no-op.
194         }
195     }
196 
setLineStyle(LineStyle style)197     public void setLineStyle(LineStyle style) {
198         int swtStyle = 0;
199         switch (style) {
200         case LINE_SOLID:
201             swtStyle = SWT.LINE_SOLID;
202             break;
203         case LINE_DASH:
204             swtStyle = SWT.LINE_DASH;
205             break;
206         case LINE_DOT:
207             swtStyle = SWT.LINE_DOT;
208             break;
209         case LINE_DASHDOT:
210             swtStyle = SWT.LINE_DASHDOT;
211             break;
212         case LINE_DASHDOTDOT:
213             swtStyle = SWT.LINE_DASHDOTDOT;
214             break;
215         default:
216             assert false : style;
217             break;
218         }
219 
220         if (swtStyle != 0) {
221             checkGC();
222             getGc().setLineStyle(swtStyle);
223         }
224     }
225 
setLineWidth(int width)226     public void setLineWidth(int width) {
227         checkGC();
228         if (width > 0) {
229             getGc().setLineWidth(width);
230         }
231     }
232 
233     // lines
234 
drawLine(int x1, int y1, int x2, int y2)235     public void drawLine(int x1, int y1, int x2, int y2) {
236         checkGC();
237         useStrokeAlpha();
238         x1 = mHScale.translate(x1);
239         y1 = mVScale.translate(y1);
240         x2 = mHScale.translate(x2);
241         y2 = mVScale.translate(y2);
242         getGc().drawLine(x1, y1, x2, y2);
243     }
244 
drawLine(Point p1, Point p2)245     public void drawLine(Point p1, Point p2) {
246         drawLine(p1.x, p1.y, p2.x, p2.y);
247     }
248 
249     // rectangles
250 
drawRect(int x1, int y1, int x2, int y2)251     public void drawRect(int x1, int y1, int x2, int y2) {
252         checkGC();
253         useStrokeAlpha();
254         int x = mHScale.translate(x1);
255         int y = mVScale.translate(y1);
256         int w = mHScale.scale(x2 - x1);
257         int h = mVScale.scale(y2 - y1);
258         getGc().drawRectangle(x, y, w, h);
259     }
260 
drawRect(Point p1, Point p2)261     public void drawRect(Point p1, Point p2) {
262         drawRect(p1.x, p1.y, p2.x, p2.y);
263     }
264 
drawRect(Rect r)265     public void drawRect(Rect r) {
266         checkGC();
267         useStrokeAlpha();
268         int x = mHScale.translate(r.x);
269         int y = mVScale.translate(r.y);
270         int w = mHScale.scale(r.w);
271         int h = mVScale.scale(r.h);
272         getGc().drawRectangle(x, y, w, h);
273     }
274 
fillRect(int x1, int y1, int x2, int y2)275     public void fillRect(int x1, int y1, int x2, int y2) {
276         checkGC();
277         useFillAlpha();
278         int x = mHScale.translate(x1);
279         int y = mVScale.translate(y1);
280         int w = mHScale.scale(x2 - x1);
281         int h = mVScale.scale(y2 - y1);
282         getGc().fillRectangle(x, y, w, h);
283     }
284 
fillRect(Point p1, Point p2)285     public void fillRect(Point p1, Point p2) {
286         fillRect(p1.x, p1.y, p2.x, p2.y);
287     }
288 
fillRect(Rect r)289     public void fillRect(Rect r) {
290         checkGC();
291         useFillAlpha();
292         int x = mHScale.translate(r.x);
293         int y = mVScale.translate(r.y);
294         int w = mHScale.scale(r.w);
295         int h = mVScale.scale(r.h);
296         getGc().fillRectangle(x, y, w, h);
297     }
298 
299     // circles (actually ovals)
300 
drawOval(int x1, int y1, int x2, int y2)301     public void drawOval(int x1, int y1, int x2, int y2) {
302         checkGC();
303         useStrokeAlpha();
304         int x = mHScale.translate(x1);
305         int y = mVScale.translate(y1);
306         int w = mHScale.scale(x2 - x1);
307         int h = mVScale.scale(y2 - y1);
308         getGc().drawOval(x, y, w, h);
309     }
310 
drawOval(Point p1, Point p2)311     public void drawOval(Point p1, Point p2) {
312         drawOval(p1.x, p1.y, p2.x, p2.y);
313     }
314 
drawOval(Rect r)315     public void drawOval(Rect r) {
316         checkGC();
317         useStrokeAlpha();
318         int x = mHScale.translate(r.x);
319         int y = mVScale.translate(r.y);
320         int w = mHScale.scale(r.w);
321         int h = mVScale.scale(r.h);
322         getGc().drawOval(x, y, w, h);
323     }
324 
fillOval(int x1, int y1, int x2, int y2)325     public void fillOval(int x1, int y1, int x2, int y2) {
326         checkGC();
327         useFillAlpha();
328         int x = mHScale.translate(x1);
329         int y = mVScale.translate(y1);
330         int w = mHScale.scale(x2 - x1);
331         int h = mVScale.scale(y2 - y1);
332         getGc().fillOval(x, y, w, h);
333     }
334 
fillOval(Point p1, Point p2)335     public void fillOval(Point p1, Point p2) {
336         fillOval(p1.x, p1.y, p2.x, p2.y);
337     }
338 
fillOval(Rect r)339     public void fillOval(Rect r) {
340         checkGC();
341         useFillAlpha();
342         int x = mHScale.translate(r.x);
343         int y = mVScale.translate(r.y);
344         int w = mHScale.scale(r.w);
345         int h = mVScale.scale(r.h);
346         getGc().fillOval(x, y, w, h);
347     }
348 
349 
350     // strings
351 
drawString(String string, int x, int y)352     public void drawString(String string, int x, int y) {
353         checkGC();
354         useStrokeAlpha();
355         x = mHScale.translate(x);
356         y = mVScale.translate(y);
357         // Background fill of text is not useful because it does not
358         // use the alpha; we instead supply a separate method (drawBoxedStrings) which
359         // first paints a semi-transparent mask for the text to sit on
360         // top of (this ensures that the text is readable regardless of
361         // colors of the pixels below the text)
362         getGc().drawString(string, x, y, true /*isTransparent*/);
363     }
364 
drawBoxedStrings(int x, int y, List<?> strings)365     public void drawBoxedStrings(int x, int y, List<?> strings) {
366         checkGC();
367 
368         x = mHScale.translate(x);
369         y = mVScale.translate(y);
370 
371         // Compute bounds of the box by adding up the sum of the text heights
372         // and the max of the text widths
373         int width = 0;
374         int height = 0;
375         int lineHeight = getGc().getFontMetrics().getHeight();
376         for (Object s : strings) {
377             org.eclipse.swt.graphics.Point extent = getGc().stringExtent(s.toString());
378             height += extent.y;
379             width = Math.max(width, extent.x);
380         }
381 
382         // Paint a box below the text
383         int padding = 2;
384         useFillAlpha();
385         getGc().fillRectangle(x - padding, y - padding, width + 2 * padding, height + 2 * padding);
386 
387         // Finally draw strings on top
388         useStrokeAlpha();
389         int lineY = y;
390         for (Object s : strings) {
391             getGc().drawString(s.toString(), x, lineY, true /* isTransparent */);
392             lineY += lineHeight;
393         }
394     }
395 
drawString(String string, Point topLeft)396     public void drawString(String string, Point topLeft) {
397         drawString(string, topLeft.x, topLeft.y);
398     }
399 
400     // Styles
401 
useStyle(DrawingStyle style)402     public void useStyle(DrawingStyle style) {
403         checkGC();
404 
405         // Look up the specific SWT style which defines the actual
406         // colors and attributes to be used for the logical drawing style.
407         SwtDrawingStyle swtStyle = SwtDrawingStyle.of(style);
408         RGB stroke = swtStyle.getStrokeColor();
409         if (stroke != null) {
410             Color color = getStrokeColor(style, stroke);
411             mGc.setForeground(color);
412         }
413         RGB fill = swtStyle.getFillColor();
414         if (fill != null) {
415             Color color = getFillColor(style, fill);
416             mGc.setBackground(color);
417         }
418         mGc.setLineWidth(swtStyle.getLineWidth());
419         mGc.setLineStyle(swtStyle.getLineStyle());
420         if (swtStyle.getLineStyle() == SWT.LINE_CUSTOM) {
421             mGc.setLineDash(new int[] {
422                     8, 4
423             });
424         }
425         mCurrentStyle = swtStyle;
426     }
427 
428     /** Uses the stroke alpha for subsequent drawing operations. */
useStrokeAlpha()429     private void useStrokeAlpha() {
430         mGc.setAlpha(mCurrentStyle.getStrokeAlpha());
431     }
432 
433     /** Uses the fill alpha for subsequent drawing operations. */
useFillAlpha()434     private void useFillAlpha() {
435         mGc.setAlpha(mCurrentStyle.getFillAlpha());
436     }
437 
438     /**
439      * Get the SWT stroke color (foreground/border) to use for the given style,
440      * using the provided color description if we haven't seen this color yet.
441      * The color will also be placed in the {@link #mStyleStrokeMap} such that
442      * it can be disposed of at cleanup time.
443      *
444      * @param style The drawing style for which we want a color
445      * @param defaultColorDesc The RGB values to initialize the color to if we
446      *            haven't seen this color before
447      * @return The color object
448      */
getStrokeColor(DrawingStyle style, RGB defaultColorDesc)449     private Color getStrokeColor(DrawingStyle style, RGB defaultColorDesc) {
450         return getStyleColor(style, defaultColorDesc, mStyleStrokeMap);
451     }
452 
453     /**
454      * Get the SWT fill (background/interior) color to use for the given style,
455      * using the provided color description if we haven't seen this color yet.
456      * The color will also be placed in the {@link #mStyleStrokeMap} such that
457      * it can be disposed of at cleanup time.
458      *
459      * @param style The drawing style for which we want a color
460      * @param defaultColorDesc The RGB values to initialize the color to if we
461      *            haven't seen this color before
462      * @return The color object
463      */
getFillColor(DrawingStyle style, RGB defaultColorDesc)464     private Color getFillColor(DrawingStyle style, RGB defaultColorDesc) {
465         return getStyleColor(style, defaultColorDesc, mStyleFillMap);
466     }
467 
468     /**
469      * Get the SWT color to use for the given style, using the provided color
470      * description if we haven't seen this color yet. The color will also be
471      * placed in the map referenced by the map parameter such that it can be
472      * disposed of at cleanup time.
473      *
474      * @param style The drawing style for which we want a color
475      * @param defaultColorDesc The RGB values to initialize the color to if we
476      *            haven't seen this color before
477      * @param map The color map to use
478      * @return The color object
479      */
getStyleColor(DrawingStyle style, RGB defaultColorDesc, Map<DrawingStyle, Color> map)480     private Color getStyleColor(DrawingStyle style, RGB defaultColorDesc,
481             Map<DrawingStyle, Color> map) {
482         Color color = map.get(style);
483         if (color == null) {
484             color = new Color(getGc().getDevice(), defaultColorDesc);
485             map.put(style, color);
486         }
487 
488         return color;
489     }
490 
491     // dots
492 
drawPoint(int x, int y)493     public void drawPoint(int x, int y) {
494         checkGC();
495         useStrokeAlpha();
496         x = mHScale.translate(x);
497         y = mVScale.translate(y);
498 
499         getGc().drawPoint(x, y);
500     }
501 
502     // arrows
503 
504     private static final int MIN_LENGTH = 10;
505 
506 
drawArrow(int x1, int y1, int x2, int y2, int size)507     public void drawArrow(int x1, int y1, int x2, int y2, int size) {
508         int arrowWidth = size;
509         int arrowHeight = size;
510 
511         checkGC();
512         useStrokeAlpha();
513         x1 = mHScale.translate(x1);
514         y1 = mVScale.translate(y1);
515         x2 = mHScale.translate(x2);
516         y2 = mVScale.translate(y2);
517         GC graphics = getGc();
518 
519         // Make size adjustments to ensure that the arrow has enough width to be visible
520         if (x1 == x2 && Math.abs(y1 - y2) < MIN_LENGTH) {
521             int delta = (MIN_LENGTH - Math.abs(y1 - y2)) / 2;
522             if (y1 < y2) {
523                 y1 -= delta;
524                 y2 += delta;
525             } else {
526                 y1 += delta;
527                 y2-= delta;
528             }
529 
530         } else if (y1 == y2 && Math.abs(x1 - x2) < MIN_LENGTH) {
531             int delta = (MIN_LENGTH - Math.abs(x1 - x2)) / 2;
532             if (x1 < x2) {
533                 x1 -= delta;
534                 x2 += delta;
535             } else {
536                 x1 += delta;
537                 x2-= delta;
538             }
539         }
540 
541         graphics.drawLine(x1, y1, x2, y2);
542 
543         // Arrowhead:
544 
545         if (x1 == x2) {
546             // Vertical
547             if (y2 > y1) {
548                 graphics.drawLine(x2 - arrowWidth, y2 - arrowHeight, x2, y2);
549                 graphics.drawLine(x2 + arrowWidth, y2 - arrowHeight, x2, y2);
550             } else {
551                 graphics.drawLine(x2 - arrowWidth, y2 + arrowHeight, x2, y2);
552                 graphics.drawLine(x2 + arrowWidth, y2 + arrowHeight, x2, y2);
553             }
554         } else if (y1 == y2) {
555             // Horizontal
556             if (x2 > x1) {
557                 graphics.drawLine(x2 - arrowHeight, y2 - arrowWidth, x2, y2);
558                 graphics.drawLine(x2 - arrowHeight, y2 + arrowWidth, x2, y2);
559             } else {
560                 graphics.drawLine(x2 + arrowHeight, y2 - arrowWidth, x2, y2);
561                 graphics.drawLine(x2 + arrowHeight, y2 + arrowWidth, x2, y2);
562             }
563         } else {
564             // Compute angle:
565             int dy = y2 - y1;
566             int dx = x2 - x1;
567             double angle = Math.atan2(dy, dx);
568             double lineLength = Math.sqrt(dy * dy + dx * dx);
569 
570             // Imagine a line of the same length as the arrow, but with angle 0.
571             // Its two arrow lines are at (-arrowWidth, -arrowHeight) relative
572             // to the endpoint (x1 + lineLength, y1) stretching up to (x2,y2).
573             // We compute the positions of (ax,ay) for the point above and
574             // below this line and paint the lines to it:
575             double ax = x1 + lineLength - arrowHeight;
576             double ay = y1 - arrowWidth;
577             int rx = (int) (Math.cos(angle) * (ax-x1) - Math.sin(angle) * (ay-y1) + x1);
578             int ry = (int) (Math.sin(angle) * (ax-x1) + Math.cos(angle) * (ay-y1) + y1);
579             graphics.drawLine(x2, y2, rx, ry);
580 
581             ay = y1 + arrowWidth;
582             rx = (int) (Math.cos(angle) * (ax-x1) - Math.sin(angle) * (ay-y1) + x1);
583             ry = (int) (Math.sin(angle) * (ax-x1) + Math.cos(angle) * (ay-y1) + y1);
584             graphics.drawLine(x2, y2, rx, ry);
585         }
586 
587         /* TODO: Experiment with filled arrow heads?
588         if (x1 == x2) {
589             // Vertical
590             if (y2 > y1) {
591                 for (int i = 0; i < arrowWidth; i++) {
592                     graphics.drawLine(x2 - arrowWidth + i, y2 - arrowWidth + i,
593                             x2 + arrowWidth - i, y2 - arrowWidth + i);
594                 }
595             } else {
596                 for (int i = 0; i < arrowWidth; i++) {
597                     graphics.drawLine(x2 - arrowWidth + i, y2 + arrowWidth - i,
598                             x2 + arrowWidth - i, y2 + arrowWidth - i);
599                 }
600             }
601         } else if (y1 == y2) {
602             // Horizontal
603             if (x2 > x1) {
604                 for (int i = 0; i < arrowHeight; i++) {
605                     graphics.drawLine(x2 - arrowHeight + i, y2 - arrowHeight + i, x2
606                             - arrowHeight + i, y2 + arrowHeight - i);
607                 }
608             } else {
609                 for (int i = 0; i < arrowHeight; i++) {
610                     graphics.drawLine(x2 + arrowHeight - i, y2 - arrowHeight + i, x2
611                             + arrowHeight - i, y2 + arrowHeight - i);
612                 }
613             }
614         } else {
615             // Arbitrary angle -- need to use trig
616             // TODO: Implement this
617         }
618         */
619     }
620 }
621