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