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