GNU Classpath (0.20) | |
Frames | No Frames |
1: /* BasicGraphicsUtils.java 2: Copyright (C) 2003, 2004, 2005 Free Software Foundation, Inc. 3: 4: This file is part of GNU Classpath. 5: 6: GNU Classpath is free software; you can redistribute it and/or modify 7: it under the terms of the GNU General Public License as published by 8: the Free Software Foundation; either version 2, or (at your option) 9: any later version. 10: 11: GNU Classpath is distributed in the hope that it will be useful, but 12: WITHOUT ANY WARRANTY; without even the implied warranty of 13: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14: General Public License for more details. 15: 16: You should have received a copy of the GNU General Public License 17: along with GNU Classpath; see the file COPYING. If not, write to the 18: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 19: 02110-1301 USA. 20: 21: Linking this library statically or dynamically with other modules is 22: making a combined work based on this library. Thus, the terms and 23: conditions of the GNU General Public License cover the whole 24: combination. 25: 26: As a special exception, the copyright holders of this library give you 27: permission to link this library with independent modules to produce an 28: executable, regardless of the license terms of these independent 29: modules, and to copy and distribute the resulting executable under 30: terms of your choice, provided that you also meet, for each linked 31: independent module, the terms and conditions of the license of that 32: module. An independent module is a module which is not derived from 33: or based on this library. If you modify this library, you may extend 34: this exception to your version of the library, but you are not 35: obligated to do so. If you do not wish to do so, delete this 36: exception statement from your version. */ 37: 38: package javax.swing.plaf.basic; 39: 40: import java.awt.Color; 41: import java.awt.Dimension; 42: import java.awt.Font; 43: import java.awt.FontMetrics; 44: import java.awt.Graphics; 45: import java.awt.Graphics2D; 46: import java.awt.Insets; 47: import java.awt.Rectangle; 48: import java.awt.font.FontRenderContext; 49: import java.awt.font.LineMetrics; 50: import java.awt.font.TextLayout; 51: import java.awt.geom.Rectangle2D; 52: 53: import javax.swing.AbstractButton; 54: import javax.swing.Icon; 55: import javax.swing.JComponent; 56: import javax.swing.SwingUtilities; 57: 58: 59: /** 60: * A utility class providing commonly used drawing and measurement 61: * routines. 62: * 63: * @author Sascha Brawer (brawer@dandelis.ch) 64: */ 65: public class BasicGraphicsUtils 66: { 67: /** 68: * Constructor. It is utterly unclear why this class should 69: * be constructable, but this is what the API specification 70: * says. 71: */ 72: public BasicGraphicsUtils() 73: { 74: // Nothing to do here. 75: } 76: 77: 78: /** 79: * Draws a rectangle that appears etched into the surface, given 80: * four colors that are used for drawing. 81: * 82: * <p><img src="doc-files/BasicGraphicsUtils-1.png" width="360" 83: * height="200" alt="[An illustration that shows which pixels 84: * get painted in what color]" /> 85: * 86: * @param g the graphics into which the rectangle is drawn. 87: * @param x the x coordinate of the rectangle. 88: * @param y the y coordinate of the rectangle. 89: * @param width the width of the rectangle in pixels. 90: * @param height the height of the rectangle in pixels. 91: * 92: * @param shadow the color that will be used for painting 93: * the outer side of the top and left edges. 94: * 95: * @param darkShadow the color that will be used for painting 96: * the inner side of the top and left edges. 97: * 98: * @param highlight the color that will be used for painting 99: * the inner side of the bottom and right edges. 100: * 101: * @param lightHighlight the color that will be used for painting 102: * the outer side of the bottom and right edges. 103: * 104: * @see #getEtchedInsets() 105: * @see javax.swing.border.EtchedBorder 106: */ 107: public static void drawEtchedRect(Graphics g, 108: int x, int y, int width, int height, 109: Color shadow, Color darkShadow, 110: Color highlight, Color lightHighlight) 111: { 112: Color oldColor; 113: int x2, y2; 114: 115: oldColor = g.getColor(); 116: x2 = x + width - 1; 117: y2 = y + height - 1; 118: 119: try 120: { 121: /* To understand this code, it might be helpful to look at the 122: * image "BasicGraphicsUtils-1.png" that is included with the 123: * JavaDoc. The file is located in the "doc-files" subdirectory. 124: * 125: * (x2, y2) is the coordinate of the most right and bottom pixel 126: * to be painted. 127: */ 128: g.setColor(shadow); 129: g.drawLine(x, y, x2 - 1, y); // top, outer 130: g.drawLine(x, y + 1, x, y2 - 1); // left, outer 131: 132: g.setColor(darkShadow); 133: g.drawLine(x + 1, y + 1, x2 - 2, y + 1); // top, inner 134: g.drawLine(x + 1, y + 2, x + 1, y2 - 2); // left, inner 135: 136: g.setColor(highlight); 137: g.drawLine(x + 1, y2 - 1, x2 - 1, y2 - 1); // bottom, inner 138: g.drawLine(x2 - 1, y + 1, x2 - 1, y2 - 2); // right, inner 139: 140: g.setColor(lightHighlight); 141: g.drawLine(x, y2, x2, y2); // bottom, outer 142: g.drawLine(x2, y, x2, y2 - 1); // right, outer 143: } 144: finally 145: { 146: g.setColor(oldColor); 147: } 148: } 149: 150: 151: /** 152: * Determines the width of the border that gets painted by 153: * {@link #drawEtchedRect}. 154: * 155: * @return an <code>Insets</code> object whose <code>top</code>, 156: * <code>left</code>, <code>bottom</code> and 157: * <code>right</code> field contain the border width at the 158: * respective edge in pixels. 159: */ 160: public static Insets getEtchedInsets() 161: { 162: return new Insets(2, 2, 2, 2); 163: } 164: 165: 166: /** 167: * Draws a rectangle that appears etched into the surface, given 168: * two colors that are used for drawing. 169: * 170: * <p><img src="doc-files/BasicGraphicsUtils-2.png" width="360" 171: * height="200" alt="[An illustration that shows which pixels 172: * get painted in what color]" /> 173: * 174: * @param g the graphics into which the rectangle is drawn. 175: * @param x the x coordinate of the rectangle. 176: * @param y the y coordinate of the rectangle. 177: * @param width the width of the rectangle in pixels. 178: * @param height the height of the rectangle in pixels. 179: * 180: * @param shadow the color that will be used for painting the outer 181: * side of the top and left edges, and for the inner side of 182: * the bottom and right ones. 183: * 184: * @param highlight the color that will be used for painting the 185: * inner side of the top and left edges, and for the outer 186: * side of the bottom and right ones. 187: * 188: * @see #getGrooveInsets() 189: * @see javax.swing.border.EtchedBorder 190: */ 191: public static void drawGroove(Graphics g, 192: int x, int y, int width, int height, 193: Color shadow, Color highlight) 194: { 195: /* To understand this, it might be helpful to look at the image 196: * "BasicGraphicsUtils-2.png" that is included with the JavaDoc, 197: * and to compare it with "BasicGraphicsUtils-1.png" which shows 198: * the pixels painted by drawEtchedRect. These image files are 199: * located in the "doc-files" subdirectory. 200: */ 201: drawEtchedRect(g, x, y, width, height, 202: /* outer topLeft */ shadow, 203: /* inner topLeft */ highlight, 204: /* inner bottomRight */ shadow, 205: /* outer bottomRight */ highlight); 206: } 207: 208: 209: /** 210: * Determines the width of the border that gets painted by 211: * {@link #drawGroove}. 212: * 213: * @return an <code>Insets</code> object whose <code>top</code>, 214: * <code>left</code>, <code>bottom</code> and 215: * <code>right</code> field contain the border width at the 216: * respective edge in pixels. 217: */ 218: public static Insets getGrooveInsets() 219: { 220: return new Insets(2, 2, 2, 2); 221: } 222: 223: 224: /** 225: * Draws a border that is suitable for buttons of the Basic look and 226: * feel. 227: * 228: * <p><img src="doc-files/BasicGraphicsUtils-3.png" width="500" 229: * height="300" alt="[An illustration that shows which pixels 230: * get painted in what color]" /> 231: * 232: * @param g the graphics into which the rectangle is drawn. 233: * @param x the x coordinate of the rectangle. 234: * @param y the y coordinate of the rectangle. 235: * @param width the width of the rectangle in pixels. 236: * @param height the height of the rectangle in pixels. 237: * 238: * @param isPressed <code>true</code> to draw the button border 239: * with a pressed-in appearance; <code>false</code> for 240: * normal (unpressed) appearance. 241: * 242: * @param isDefault <code>true</code> to draw the border with 243: * the appearance it has when hitting the enter key in a 244: * dialog will simulate a click to this button; 245: * <code>false</code> for normal appearance. 246: * 247: * @param shadow the shadow color. 248: * @param darkShadow a darker variant of the shadow color. 249: * @param highlight the highlight color. 250: * @param lightHighlight a brighter variant of the highlight color. 251: */ 252: public static void drawBezel(Graphics g, 253: int x, int y, int width, int height, 254: boolean isPressed, boolean isDefault, 255: Color shadow, Color darkShadow, 256: Color highlight, Color lightHighlight) 257: { 258: Color oldColor = g.getColor(); 259: 260: /* To understand this, it might be helpful to look at the image 261: * "BasicGraphicsUtils-3.png" that is included with the JavaDoc, 262: * and to compare it with "BasicGraphicsUtils-1.png" which shows 263: * the pixels painted by drawEtchedRect. These image files are 264: * located in the "doc-files" subdirectory. 265: */ 266: try 267: { 268: if ((isPressed == false) && (isDefault == false)) 269: { 270: drawEtchedRect(g, x, y, width, height, 271: lightHighlight, highlight, 272: shadow, darkShadow); 273: } 274: 275: if ((isPressed == true) && (isDefault == false)) 276: { 277: g.setColor(shadow); 278: g.drawRect(x + 1, y + 1, width - 2, height - 2); 279: } 280: 281: if ((isPressed == false) && (isDefault == true)) 282: { 283: g.setColor(darkShadow); 284: g.drawRect(x, y, width - 1, height - 1); 285: drawEtchedRect(g, x + 1, y + 1, width - 2, height - 2, 286: lightHighlight, highlight, 287: shadow, darkShadow); 288: } 289: 290: if ((isPressed == true) && (isDefault == true)) 291: { 292: g.setColor(darkShadow); 293: g.drawRect(x, y, width - 1, height - 1); 294: g.setColor(shadow); 295: g.drawRect(x + 1, y + 1, width - 3, height - 3); 296: } 297: } 298: finally 299: { 300: g.setColor(oldColor); 301: } 302: } 303: 304: 305: /** 306: * Draws a rectangle that appears lowered into the surface, given 307: * four colors that are used for drawing. 308: * 309: * <p><img src="doc-files/BasicGraphicsUtils-4.png" width="360" 310: * height="200" alt="[An illustration that shows which pixels 311: * get painted in what color]" /> 312: * 313: * <p><strong>Compatibility with the Sun reference 314: * implementation:</strong> The Sun reference implementation seems 315: * to ignore the <code>x</code> and <code>y</code> arguments, at 316: * least in JDK 1.3.1 and 1.4.1_01. The method always draws the 317: * rectangular area at location (0, 0). A bug report has been filed 318: * with Sun; its “bug ID” is 4880003. The GNU Classpath 319: * implementation behaves correctly, thus not replicating this bug. 320: * 321: * @param g the graphics into which the rectangle is drawn. 322: * @param x the x coordinate of the rectangle. 323: * @param y the y coordinate of the rectangle. 324: * @param width the width of the rectangle in pixels. 325: * @param height the height of the rectangle in pixels. 326: * 327: * @param shadow the color that will be used for painting 328: * the inner side of the top and left edges. 329: * 330: * @param darkShadow the color that will be used for painting 331: * the outer side of the top and left edges. 332: * 333: * @param highlight the color that will be used for painting 334: * the inner side of the bottom and right edges. 335: * 336: * @param lightHighlight the color that will be used for painting 337: * the outer side of the bottom and right edges. 338: */ 339: public static void drawLoweredBezel(Graphics g, 340: int x, int y, int width, int height, 341: Color shadow, Color darkShadow, 342: Color highlight, Color lightHighlight) 343: { 344: /* Like drawEtchedRect, but swapping darkShadow and shadow. 345: * 346: * To understand this, it might be helpful to look at the image 347: * "BasicGraphicsUtils-4.png" that is included with the JavaDoc, 348: * and to compare it with "BasicGraphicsUtils-1.png" which shows 349: * the pixels painted by drawEtchedRect. These image files are 350: * located in the "doc-files" subdirectory. 351: */ 352: drawEtchedRect(g, x, y, width, height, 353: darkShadow, shadow, 354: highlight, lightHighlight); 355: } 356: 357: 358: /** 359: * Draws a String at the given location, underlining the first 360: * occurence of a specified character. The algorithm for determining 361: * the underlined position is not sensitive to case. If the 362: * character is not part of <code>text</code>, the text will be 363: * drawn without underlining. Drawing is performed in the current 364: * color and font of <code>g</code>. 365: * 366: * <p><img src="doc-files/BasicGraphicsUtils-5.png" width="500" 367: * height="100" alt="[An illustration showing how to use the 368: * method]" /> 369: * 370: * @param g the graphics into which the String is drawn. 371: * 372: * @param text the String to draw. 373: * 374: * @param underlinedChar the character whose first occurence in 375: * <code>text</code> will be underlined. It is not clear 376: * why the API specification declares this argument to be 377: * of type <code>int</code> instead of <code>char</code>. 378: * While this would allow to pass Unicode characters outside 379: * Basic Multilingual Plane 0 (U+0000 .. U+FFFE), at least 380: * the GNU Classpath implementation does not underline 381: * anything if <code>underlinedChar</code> is outside 382: * the range of <code>char</code>. 383: * 384: * @param x the x coordinate of the text, as it would be passed to 385: * {@link java.awt.Graphics#drawString(java.lang.String, 386: * int, int)}. 387: * 388: * @param y the y coordinate of the text, as it would be passed to 389: * {@link java.awt.Graphics#drawString(java.lang.String, 390: * int, int)}. 391: */ 392: public static void drawString(Graphics g, String text, 393: int underlinedChar, int x, int y) 394: { 395: int index = -1; 396: 397: /* It is intentional that lower case is used. In some languages, 398: * the set of lowercase characters is larger than the set of 399: * uppercase ones. Therefore, it is good practice to use lowercase 400: * for such comparisons (which really means that the author of this 401: * code can vaguely remember having read some Unicode techreport 402: * with this recommendation, but is too lazy to look for the URL). 403: */ 404: if ((underlinedChar >= 0) || (underlinedChar <= 0xffff)) 405: index = text.toLowerCase().indexOf( 406: Character.toLowerCase((char) underlinedChar)); 407: 408: drawStringUnderlineCharAt(g, text, index, x, y); 409: } 410: 411: 412: /** 413: * Draws a String at the given location, underlining the character 414: * at the specified index. Drawing is performed in the current color 415: * and font of <code>g</code>. 416: * 417: * <p><img src="doc-files/BasicGraphicsUtils-5.png" width="500" 418: * height="100" alt="[An illustration showing how to use the 419: * method]" /> 420: * 421: * @param g the graphics into which the String is drawn. 422: * 423: * @param text the String to draw. 424: * 425: * @param underlinedIndex the index of the underlined character in 426: * <code>text</code>. If <code>underlinedIndex</code> falls 427: * outside the range <code>[0, text.length() - 1]</code>, the 428: * text will be drawn without underlining anything. 429: * 430: * @param x the x coordinate of the text, as it would be passed to 431: * {@link java.awt.Graphics#drawString(java.lang.String, 432: * int, int)}. 433: * 434: * @param y the y coordinate of the text, as it would be passed to 435: * {@link java.awt.Graphics#drawString(java.lang.String, 436: * int, int)}. 437: * 438: * @since 1.4 439: */ 440: public static void drawStringUnderlineCharAt(Graphics g, String text, 441: int underlinedIndex, 442: int x, int y) 443: { 444: Graphics2D g2; 445: Rectangle2D.Double underline; 446: FontRenderContext frc; 447: FontMetrics fmet; 448: LineMetrics lineMetrics; 449: Font font; 450: TextLayout layout; 451: double underlineX1, underlineX2; 452: boolean drawUnderline; 453: int textLength; 454: 455: textLength = text.length(); 456: if (textLength == 0) 457: return; 458: 459: drawUnderline = (underlinedIndex >= 0) && (underlinedIndex < textLength); 460: 461: // FIXME: unfortunately pango and cairo can't agree on metrics 462: // so for the time being we continue to *not* use TextLayouts. 463: if (true || !(g instanceof Graphics2D)) 464: { 465: /* Fall-back. This is likely to produce garbage for any text 466: * containing right-to-left (Hebrew or Arabic) characters, even 467: * if the underlined character is left-to-right. 468: */ 469: g.drawString(text, x, y); 470: if (drawUnderline) 471: { 472: fmet = g.getFontMetrics(); 473: g.fillRect( 474: /* x */ x + fmet.stringWidth(text.substring(0, underlinedIndex)), 475: /* y */ y + fmet.getDescent() - 1, 476: /* width */ fmet.charWidth(text.charAt(underlinedIndex)), 477: /* height */ 1); 478: } 479: 480: return; 481: } 482: 483: g2 = (Graphics2D) g; 484: font = g2.getFont(); 485: frc = g2.getFontRenderContext(); 486: lineMetrics = font.getLineMetrics(text, frc); 487: layout = new TextLayout(text, font, frc); 488: 489: /* Draw the text. */ 490: layout.draw(g2, x, y); 491: if (!drawUnderline) 492: return; 493: 494: underlineX1 = x + layout.getLogicalHighlightShape( 495: underlinedIndex, underlinedIndex).getBounds2D().getX(); 496: underlineX2 = x + layout.getLogicalHighlightShape( 497: underlinedIndex + 1, underlinedIndex + 1).getBounds2D().getX(); 498: 499: underline = new Rectangle2D.Double(); 500: if (underlineX1 < underlineX2) 501: { 502: underline.x = underlineX1; 503: underline.width = underlineX2 - underlineX1; 504: } 505: else 506: { 507: underline.x = underlineX2; 508: underline.width = underlineX1 - underlineX2; 509: } 510: 511: 512: underline.height = lineMetrics.getUnderlineThickness(); 513: underline.y = lineMetrics.getUnderlineOffset(); 514: if (underline.y == 0) 515: { 516: /* Some fonts do not specify an underline offset, although they 517: * actually should do so. In that case, the result of calling 518: * lineMetrics.getUnderlineOffset() will be zero. Since it would 519: * look very ugly if the underline was be positioned immediately 520: * below the baseline, we check for this and move the underline 521: * below the descent, as shown in the following ASCII picture: 522: * 523: * ##### ##### # 524: * # # # # 525: * # # # # 526: * # # # # 527: * ##### ###### ---- baseline (0) 528: * # 529: * # 530: * ------------------###----------- lineMetrics.getDescent() 531: */ 532: underline.y = lineMetrics.getDescent(); 533: } 534: 535: underline.y += y; 536: g2.fill(underline); 537: } 538: 539: 540: /** 541: * Draws a rectangle, simulating a dotted stroke by painting only 542: * every second pixel along the one-pixel thick edge. The color of 543: * those pixels is the current color of the Graphics <code>g</code>. 544: * Any other pixels are left unchanged. 545: * 546: * <p><img src="doc-files/BasicGraphicsUtils-7.png" width="360" 547: * height="200" alt="[An illustration that shows which pixels 548: * get painted]" /> 549: * 550: * @param g the graphics into which the rectangle is drawn. 551: * @param x the x coordinate of the rectangle. 552: * @param y the y coordinate of the rectangle. 553: * @param width the width of the rectangle in pixels. 554: * @param height the height of the rectangle in pixels. 555: */ 556: public static void drawDashedRect(Graphics g, 557: int x, int y, int width, int height) 558: { 559: int right = x + width - 1; 560: int bottom = y + height - 1; 561: 562: /* Draw the top and bottom edge of the dotted rectangle. */ 563: for (int i = x; i <= right; i += 2) 564: { 565: g.drawLine(i, y, i, y); 566: g.drawLine(i, bottom, i, bottom); 567: } 568: 569: /* Draw the left and right edge of the dotted rectangle. */ 570: for (int i = y; i <= bottom; i += 2) 571: { 572: g.drawLine(x, i, x, i); 573: g.drawLine(right, i, right, i); 574: } 575: } 576: 577: 578: /** 579: * Determines the preferred width and height of an AbstractButton, 580: * given the gap between the button’s text and icon. 581: * 582: * @param b the button whose preferred size is determined. 583: * 584: * @param textIconGap the gap between the button’s text and 585: * icon. 586: * 587: * @return a <code>Dimension</code> object whose <code>width</code> 588: * and <code>height</code> fields indicate the preferred 589: * extent in pixels. 590: * 591: * @see javax.swing.SwingUtilities#layoutCompoundLabel(JComponent, 592: * FontMetrics, String, Icon, int, int, int, int, Rectangle, Rectangle, 593: * Rectangle, int) 594: */ 595: public static Dimension getPreferredButtonSize(AbstractButton b, 596: int textIconGap) 597: { 598: Rectangle contentRect; 599: Rectangle viewRect; 600: Rectangle iconRect = new Rectangle(); 601: Rectangle textRect = new Rectangle(); 602: Insets insets = b.getInsets(); 603: 604: viewRect = new Rectangle(); 605: 606: /* java.awt.Toolkit.getFontMetrics is deprecated. However, it 607: * seems not obvious how to get to the correct FontMetrics object 608: * otherwise. The real problem probably is that the method 609: * javax.swing.SwingUtilities.layoutCompundLabel should take a 610: * LineMetrics, not a FontMetrics argument. But fixing this that 611: * would change the public API. 612: */ 613: SwingUtilities.layoutCompoundLabel( 614: b, // for the component orientation 615: b.getToolkit().getFontMetrics(b.getFont()), // see comment above 616: b.getText(), 617: b.getIcon(), 618: b.getVerticalAlignment(), 619: b.getHorizontalAlignment(), 620: b.getVerticalTextPosition(), 621: b.getHorizontalTextPosition(), 622: viewRect, iconRect, textRect, 623: textIconGap); 624: 625: /* +------------------------+ +------------------------+ 626: * | | | | 627: * | ICON | | CONTENTCONTENTCONTENT | 628: * | TEXTTEXTTEXT | --> | CONTENTCONTENTCONTENT | 629: * | TEXTTEXTTEXT | | CONTENTCONTENTCONTENT | 630: * +------------------------+ +------------------------+ 631: */ 632: 633: contentRect = textRect.union(iconRect); 634: 635: return new Dimension(insets.left 636: + contentRect.width 637: + insets.right + b.getHorizontalAlignment(), 638: insets.top 639: + contentRect.height 640: + insets.bottom); 641: } 642: }
GNU Classpath (0.20) |