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 gnu.classpath.SystemProperties; 41: 42: import java.awt.Color; 43: import java.awt.Dimension; 44: import java.awt.Font; 45: import java.awt.FontMetrics; 46: import java.awt.Graphics; 47: import java.awt.Graphics2D; 48: import java.awt.Insets; 49: import java.awt.Rectangle; 50: import java.awt.font.FontRenderContext; 51: import java.awt.font.LineMetrics; 52: import java.awt.font.TextLayout; 53: import java.awt.geom.Rectangle2D; 54: 55: import javax.swing.AbstractButton; 56: import javax.swing.Icon; 57: import javax.swing.JComponent; 58: import javax.swing.SwingUtilities; 59: 60: 61: /** 62: * A utility class providing commonly used drawing and measurement 63: * routines. 64: * 65: * @author Sascha Brawer (brawer@dandelis.ch) 66: */ 67: public class BasicGraphicsUtils 68: { 69: /** 70: * Used as a key for a client property to store cached TextLayouts in. This 71: * is used for speed-up drawing of text in 72: * {@link #drawString(Graphics, String, int, int, int)}. 73: */ 74: static final String CACHED_TEXT_LAYOUT = 75: "BasicGraphicsUtils.cachedTextLayout"; 76: 77: /** 78: * Constructor. It is utterly unclear why this class should 79: * be constructable, but this is what the API specification 80: * says. 81: */ 82: public BasicGraphicsUtils() 83: { 84: // Nothing to do here. 85: } 86: 87: 88: /** 89: * Draws a rectangle that appears etched into the surface, given 90: * four colors that are used for drawing. 91: * 92: * <p><img src="doc-files/BasicGraphicsUtils-1.png" width="360" 93: * height="200" alt="[An illustration that shows which pixels 94: * get painted in what color]" /> 95: * 96: * @param g the graphics into which the rectangle is drawn. 97: * @param x the x coordinate of the rectangle. 98: * @param y the y coordinate of the rectangle. 99: * @param width the width of the rectangle in pixels. 100: * @param height the height of the rectangle in pixels. 101: * 102: * @param shadow the color that will be used for painting 103: * the outer side of the top and left edges. 104: * 105: * @param darkShadow the color that will be used for painting 106: * the inner side of the top and left edges. 107: * 108: * @param highlight the color that will be used for painting 109: * the inner side of the bottom and right edges. 110: * 111: * @param lightHighlight the color that will be used for painting 112: * the outer side of the bottom and right edges. 113: * 114: * @see #getEtchedInsets() 115: * @see javax.swing.border.EtchedBorder 116: */ 117: public static void drawEtchedRect(Graphics g, 118: int x, int y, int width, int height, 119: Color shadow, Color darkShadow, 120: Color highlight, Color lightHighlight) 121: { 122: Color oldColor; 123: int x2, y2; 124: 125: oldColor = g.getColor(); 126: x2 = x + width - 1; 127: y2 = y + height - 1; 128: 129: try 130: { 131: /* To understand this code, it might be helpful to look at the 132: * image "BasicGraphicsUtils-1.png" that is included with the 133: * JavaDoc. The file is located in the "doc-files" subdirectory. 134: * 135: * (x2, y2) is the coordinate of the most right and bottom pixel 136: * to be painted. 137: */ 138: g.setColor(shadow); 139: g.drawLine(x, y, x2 - 1, y); // top, outer 140: g.drawLine(x, y + 1, x, y2 - 1); // left, outer 141: 142: g.setColor(darkShadow); 143: g.drawLine(x + 1, y + 1, x2 - 2, y + 1); // top, inner 144: g.drawLine(x + 1, y + 2, x + 1, y2 - 2); // left, inner 145: 146: g.setColor(highlight); 147: g.drawLine(x + 1, y2 - 1, x2 - 1, y2 - 1); // bottom, inner 148: g.drawLine(x2 - 1, y + 1, x2 - 1, y2 - 2); // right, inner 149: 150: g.setColor(lightHighlight); 151: g.drawLine(x, y2, x2, y2); // bottom, outer 152: g.drawLine(x2, y, x2, y2 - 1); // right, outer 153: } 154: finally 155: { 156: g.setColor(oldColor); 157: } 158: } 159: 160: 161: /** 162: * Determines the width of the border that gets painted by 163: * {@link #drawEtchedRect}. 164: * 165: * @return an <code>Insets</code> object whose <code>top</code>, 166: * <code>left</code>, <code>bottom</code> and 167: * <code>right</code> field contain the border width at the 168: * respective edge in pixels. 169: */ 170: public static Insets getEtchedInsets() 171: { 172: return new Insets(2, 2, 2, 2); 173: } 174: 175: 176: /** 177: * Draws a rectangle that appears etched into the surface, given 178: * two colors that are used for drawing. 179: * 180: * <p><img src="doc-files/BasicGraphicsUtils-2.png" width="360" 181: * height="200" alt="[An illustration that shows which pixels 182: * get painted in what color]" /> 183: * 184: * @param g the graphics into which the rectangle is drawn. 185: * @param x the x coordinate of the rectangle. 186: * @param y the y coordinate of the rectangle. 187: * @param width the width of the rectangle in pixels. 188: * @param height the height of the rectangle in pixels. 189: * 190: * @param shadow the color that will be used for painting the outer 191: * side of the top and left edges, and for the inner side of 192: * the bottom and right ones. 193: * 194: * @param highlight the color that will be used for painting the 195: * inner side of the top and left edges, and for the outer 196: * side of the bottom and right ones. 197: * 198: * @see #getGrooveInsets() 199: * @see javax.swing.border.EtchedBorder 200: */ 201: public static void drawGroove(Graphics g, 202: int x, int y, int width, int height, 203: Color shadow, Color highlight) 204: { 205: /* To understand this, it might be helpful to look at the image 206: * "BasicGraphicsUtils-2.png" that is included with the JavaDoc, 207: * and to compare it with "BasicGraphicsUtils-1.png" which shows 208: * the pixels painted by drawEtchedRect. These image files are 209: * located in the "doc-files" subdirectory. 210: */ 211: drawEtchedRect(g, x, y, width, height, 212: /* outer topLeft */ shadow, 213: /* inner topLeft */ highlight, 214: /* inner bottomRight */ shadow, 215: /* outer bottomRight */ highlight); 216: } 217: 218: 219: /** 220: * Determines the width of the border that gets painted by 221: * {@link #drawGroove}. 222: * 223: * @return an <code>Insets</code> object whose <code>top</code>, 224: * <code>left</code>, <code>bottom</code> and 225: * <code>right</code> field contain the border width at the 226: * respective edge in pixels. 227: */ 228: public static Insets getGrooveInsets() 229: { 230: return new Insets(2, 2, 2, 2); 231: } 232: 233: 234: /** 235: * Draws a border that is suitable for buttons of the Basic look and 236: * feel. 237: * 238: * <p><img src="doc-files/BasicGraphicsUtils-3.png" width="500" 239: * height="300" alt="[An illustration that shows which pixels 240: * get painted in what color]" /> 241: * 242: * @param g the graphics into which the rectangle is drawn. 243: * @param x the x coordinate of the rectangle. 244: * @param y the y coordinate of the rectangle. 245: * @param width the width of the rectangle in pixels. 246: * @param height the height of the rectangle in pixels. 247: * 248: * @param isPressed <code>true</code> to draw the button border 249: * with a pressed-in appearance; <code>false</code> for 250: * normal (unpressed) appearance. 251: * 252: * @param isDefault <code>true</code> to draw the border with 253: * the appearance it has when hitting the enter key in a 254: * dialog will simulate a click to this button; 255: * <code>false</code> for normal appearance. 256: * 257: * @param shadow the shadow color. 258: * @param darkShadow a darker variant of the shadow color. 259: * @param highlight the highlight color. 260: * @param lightHighlight a brighter variant of the highlight color. 261: */ 262: public static void drawBezel(Graphics g, 263: int x, int y, int width, int height, 264: boolean isPressed, boolean isDefault, 265: Color shadow, Color darkShadow, 266: Color highlight, Color lightHighlight) 267: { 268: Color oldColor = g.getColor(); 269: 270: /* To understand this, it might be helpful to look at the image 271: * "BasicGraphicsUtils-3.png" that is included with the JavaDoc, 272: * and to compare it with "BasicGraphicsUtils-1.png" which shows 273: * the pixels painted by drawEtchedRect. These image files are 274: * located in the "doc-files" subdirectory. 275: */ 276: try 277: { 278: if ((isPressed == false) && (isDefault == false)) 279: { 280: drawEtchedRect(g, x, y, width, height, 281: lightHighlight, highlight, 282: shadow, darkShadow); 283: } 284: 285: if ((isPressed == true) && (isDefault == false)) 286: { 287: g.setColor(shadow); 288: g.drawRect(x + 1, y + 1, width - 2, height - 2); 289: } 290: 291: if ((isPressed == false) && (isDefault == true)) 292: { 293: g.setColor(darkShadow); 294: g.drawRect(x, y, width - 1, height - 1); 295: drawEtchedRect(g, x + 1, y + 1, width - 2, height - 2, 296: lightHighlight, highlight, 297: shadow, darkShadow); 298: } 299: 300: if ((isPressed == true) && (isDefault == true)) 301: { 302: g.setColor(darkShadow); 303: g.drawRect(x, y, width - 1, height - 1); 304: g.setColor(shadow); 305: g.drawRect(x + 1, y + 1, width - 3, height - 3); 306: } 307: } 308: finally 309: { 310: g.setColor(oldColor); 311: } 312: } 313: 314: 315: /** 316: * Draws a rectangle that appears lowered into the surface, given 317: * four colors that are used for drawing. 318: * 319: * <p><img src="doc-files/BasicGraphicsUtils-4.png" width="360" 320: * height="200" alt="[An illustration that shows which pixels 321: * get painted in what color]" /> 322: * 323: * <p><strong>Compatibility with the Sun reference 324: * implementation:</strong> The Sun reference implementation seems 325: * to ignore the <code>x</code> and <code>y</code> arguments, at 326: * least in JDK 1.3.1 and 1.4.1_01. The method always draws the 327: * rectangular area at location (0, 0). A bug report has been filed 328: * with Sun; its “bug ID” is 4880003. The GNU Classpath 329: * implementation behaves correctly, thus not replicating this bug. 330: * 331: * @param g the graphics into which the rectangle is drawn. 332: * @param x the x coordinate of the rectangle. 333: * @param y the y coordinate of the rectangle. 334: * @param width the width of the rectangle in pixels. 335: * @param height the height of the rectangle in pixels. 336: * 337: * @param shadow the color that will be used for painting 338: * the inner side of the top and left edges. 339: * 340: * @param darkShadow the color that will be used for painting 341: * the outer side of the top and left edges. 342: * 343: * @param highlight the color that will be used for painting 344: * the inner side of the bottom and right edges. 345: * 346: * @param lightHighlight the color that will be used for painting 347: * the outer side of the bottom and right edges. 348: */ 349: public static void drawLoweredBezel(Graphics g, 350: int x, int y, int width, int height, 351: Color shadow, Color darkShadow, 352: Color highlight, Color lightHighlight) 353: { 354: /* Like drawEtchedRect, but swapping darkShadow and shadow. 355: * 356: * To understand this, it might be helpful to look at the image 357: * "BasicGraphicsUtils-4.png" that is included with the JavaDoc, 358: * and to compare it with "BasicGraphicsUtils-1.png" which shows 359: * the pixels painted by drawEtchedRect. These image files are 360: * located in the "doc-files" subdirectory. 361: */ 362: drawEtchedRect(g, x, y, width, height, 363: darkShadow, shadow, 364: highlight, lightHighlight); 365: } 366: 367: 368: /** 369: * Draws a String at the given location, underlining the first 370: * occurence of a specified character. The algorithm for determining 371: * the underlined position is not sensitive to case. If the 372: * character is not part of <code>text</code>, the text will be 373: * drawn without underlining. Drawing is performed in the current 374: * color and font of <code>g</code>. 375: * 376: * <p><img src="doc-files/BasicGraphicsUtils-5.png" width="500" 377: * height="100" alt="[An illustration showing how to use the 378: * method]" /> 379: * 380: * @param g the graphics into which the String is drawn. 381: * 382: * @param text the String to draw. 383: * 384: * @param underlinedChar the character whose first occurence in 385: * <code>text</code> will be underlined. It is not clear 386: * why the API specification declares this argument to be 387: * of type <code>int</code> instead of <code>char</code>. 388: * While this would allow to pass Unicode characters outside 389: * Basic Multilingual Plane 0 (U+0000 .. U+FFFE), at least 390: * the GNU Classpath implementation does not underline 391: * anything if <code>underlinedChar</code> is outside 392: * the range of <code>char</code>. 393: * 394: * @param x the x coordinate of the text, as it would be passed to 395: * {@link java.awt.Graphics#drawString(java.lang.String, 396: * int, int)}. 397: * 398: * @param y the y coordinate of the text, as it would be passed to 399: * {@link java.awt.Graphics#drawString(java.lang.String, 400: * int, int)}. 401: */ 402: public static void drawString(Graphics g, String text, 403: int underlinedChar, int x, int y) 404: { 405: int index = -1; 406: 407: /* It is intentional that lower case is used. In some languages, 408: * the set of lowercase characters is larger than the set of 409: * uppercase ones. Therefore, it is good practice to use lowercase 410: * for such comparisons (which really means that the author of this 411: * code can vaguely remember having read some Unicode techreport 412: * with this recommendation, but is too lazy to look for the URL). 413: */ 414: if ((underlinedChar >= 0) || (underlinedChar <= 0xffff)) 415: index = text.toLowerCase().indexOf( 416: Character.toLowerCase((char) underlinedChar)); 417: 418: drawStringUnderlineCharAt(g, text, index, x, y); 419: } 420: 421: 422: /** 423: * Draws a String at the given location, underlining the character 424: * at the specified index. Drawing is performed in the current color 425: * and font of <code>g</code>. 426: * 427: * <p><img src="doc-files/BasicGraphicsUtils-5.png" width="500" 428: * height="100" alt="[An illustration showing how to use the 429: * method]" /> 430: * 431: * @param g the graphics into which the String is drawn. 432: * 433: * @param text the String to draw. 434: * 435: * @param underlinedIndex the index of the underlined character in 436: * <code>text</code>. If <code>underlinedIndex</code> falls 437: * outside the range <code>[0, text.length() - 1]</code>, the 438: * text will be drawn without underlining anything. 439: * 440: * @param x the x coordinate of the text, as it would be passed to 441: * {@link java.awt.Graphics#drawString(java.lang.String, 442: * int, int)}. 443: * 444: * @param y the y coordinate of the text, as it would be passed to 445: * {@link java.awt.Graphics#drawString(java.lang.String, 446: * int, int)}. 447: * 448: * @since 1.4 449: */ 450: public static void drawStringUnderlineCharAt(Graphics g, String text, 451: int underlinedIndex, 452: int x, int y) 453: { 454: Graphics2D g2; 455: Rectangle2D.Double underline; 456: FontRenderContext frc; 457: FontMetrics fmet; 458: LineMetrics lineMetrics; 459: Font font; 460: TextLayout layout; 461: double underlineX1, underlineX2; 462: boolean drawUnderline; 463: int textLength; 464: 465: textLength = text.length(); 466: if (textLength == 0) 467: return; 468: 469: drawUnderline = (underlinedIndex >= 0) && (underlinedIndex < textLength); 470: 471: // FIXME: unfortunately pango and cairo can't agree on metrics 472: // so for the time being we continue to *not* use TextLayouts. 473: if (true || !(g instanceof Graphics2D)) 474: { 475: /* Fall-back. This is likely to produce garbage for any text 476: * containing right-to-left (Hebrew or Arabic) characters, even 477: * if the underlined character is left-to-right. 478: */ 479: g.drawString(text, x, y); 480: if (drawUnderline) 481: { 482: fmet = g.getFontMetrics(); 483: g.fillRect( 484: /* x */ x + fmet.stringWidth(text.substring(0, underlinedIndex)), 485: /* y */ y + fmet.getDescent() - 1, 486: /* width */ fmet.charWidth(text.charAt(underlinedIndex)), 487: /* height */ 1); 488: } 489: 490: return; 491: } 492: 493: g2 = (Graphics2D) g; 494: font = g2.getFont(); 495: frc = g2.getFontRenderContext(); 496: lineMetrics = font.getLineMetrics(text, frc); 497: layout = new TextLayout(text, font, frc); 498: 499: /* Draw the text. */ 500: layout.draw(g2, x, y); 501: if (!drawUnderline) 502: return; 503: 504: underlineX1 = x + layout.getLogicalHighlightShape( 505: underlinedIndex, underlinedIndex).getBounds2D().getX(); 506: underlineX2 = x + layout.getLogicalHighlightShape( 507: underlinedIndex + 1, underlinedIndex + 1).getBounds2D().getX(); 508: 509: underline = new Rectangle2D.Double(); 510: if (underlineX1 < underlineX2) 511: { 512: underline.x = underlineX1; 513: underline.width = underlineX2 - underlineX1; 514: } 515: else 516: { 517: underline.x = underlineX2; 518: underline.width = underlineX1 - underlineX2; 519: } 520: 521: 522: underline.height = lineMetrics.getUnderlineThickness(); 523: underline.y = lineMetrics.getUnderlineOffset(); 524: if (underline.y == 0) 525: { 526: /* Some fonts do not specify an underline offset, although they 527: * actually should do so. In that case, the result of calling 528: * lineMetrics.getUnderlineOffset() will be zero. Since it would 529: * look very ugly if the underline was be positioned immediately 530: * below the baseline, we check for this and move the underline 531: * below the descent, as shown in the following ASCII picture: 532: * 533: * ##### ##### # 534: * # # # # 535: * # # # # 536: * # # # # 537: * ##### ###### ---- baseline (0) 538: * # 539: * # 540: * ------------------###----------- lineMetrics.getDescent() 541: */ 542: underline.y = lineMetrics.getDescent(); 543: } 544: 545: underline.y += y; 546: g2.fill(underline); 547: } 548: 549: /** 550: * Draws a string on the specified component. 551: * 552: * @param c the component 553: * @param g the Graphics context 554: * @param text the string 555: * @param underlinedChar the character to be underlined 556: * @param x the X location 557: * @param y the Y location 558: */ 559: static void drawString(JComponent c, Graphics g, String text, 560: int underlinedChar, int x, int y) 561: { 562: int index = -1; 563: 564: /* It is intentional that lower case is used. In some languages, 565: * the set of lowercase characters is larger than the set of 566: * uppercase ones. Therefore, it is good practice to use lowercase 567: * for such comparisons (which really means that the author of this 568: * code can vaguely remember having read some Unicode techreport 569: * with this recommendation, but is too lazy to look for the URL). 570: */ 571: if ((underlinedChar >= 0) || (underlinedChar <= 0xffff)) 572: index = text.toLowerCase().indexOf( 573: Character.toLowerCase((char) underlinedChar)); 574: 575: drawStringUnderlineCharAt(c, g, text, index, x, y); 576: } 577: 578: 579: /** 580: * Draws a String at the given location, underlining the character 581: * at the specified index. Drawing is performed in the current color 582: * and font of <code>g</code>. 583: * 584: * <p><img src="doc-files/BasicGraphicsUtils-5.png" width="500" 585: * height="100" alt="[An illustration showing how to use the 586: * method]" /> 587: * 588: * This is an accelerated version of the method with the same name. It 589: * uses a pre-laid out TextLayout stored in a client property. 590: * 591: * @param c the component that is drawn 592: * @param g the graphics into which the String is drawn. 593: * 594: * @param text the String to draw. 595: * 596: * @param underlinedIndex the index of the underlined character in 597: * <code>text</code>. If <code>underlinedIndex</code> falls 598: * outside the range <code>[0, text.length() - 1]</code>, the 599: * text will be drawn without underlining anything. 600: * 601: * @param x the x coordinate of the text, as it would be passed to 602: * {@link java.awt.Graphics#drawString(java.lang.String, 603: * int, int)}. 604: * 605: * @param y the y coordinate of the text, as it would be passed to 606: * {@link java.awt.Graphics#drawString(java.lang.String, 607: * int, int)}. 608: */ 609: static void drawStringUnderlineCharAt(JComponent c, Graphics g, String text, 610: int underlinedIndex, 611: int x, int y) 612: { 613: Graphics2D g2; 614: Rectangle2D.Double underline; 615: FontRenderContext frc; 616: FontMetrics fmet; 617: LineMetrics lineMetrics; 618: Font font; 619: TextLayout layout; 620: double underlineX1, underlineX2; 621: boolean drawUnderline; 622: int textLength; 623: 624: textLength = text.length(); 625: if (textLength == 0) 626: return; 627: 628: drawUnderline = (underlinedIndex >= 0) && (underlinedIndex < textLength); 629: 630: // FIXME: unfortunately pango and cairo can't agree on metrics 631: // so for the time being we continue to *not* use TextLayouts. 632: if (!(g instanceof Graphics2D) 633: || SystemProperties.getProperty("gnu.javax.swing.noGraphics2D") != null) 634: { 635: /* Fall-back. This is likely to produce garbage for any text 636: * containing right-to-left (Hebrew or Arabic) characters, even 637: * if the underlined character is left-to-right. 638: */ 639: g.drawString(text, x, y); 640: if (drawUnderline) 641: { 642: fmet = g.getFontMetrics(); 643: g.fillRect( 644: /* x */ x + fmet.stringWidth(text.substring(0, underlinedIndex)), 645: /* y */ y + 1, 646: /* width */ fmet.charWidth(text.charAt(underlinedIndex)), 647: /* height */ 1); 648: } 649: 650: return; 651: } 652: 653: g2 = (Graphics2D) g; 654: font = g2.getFont(); 655: frc = g2.getFontRenderContext(); 656: lineMetrics = font.getLineMetrics(text, frc); 657: layout = (TextLayout) c.getClientProperty(CACHED_TEXT_LAYOUT); 658: if (layout == null) 659: { 660: layout = new TextLayout(text, font, frc); 661: System.err.println("Unable to use cached TextLayout for: " + text); 662: } 663: 664: /* Draw the text. */ 665: layout.draw(g2, x, y); 666: if (!drawUnderline) 667: return; 668: 669: underlineX1 = x + layout.getLogicalHighlightShape( 670: underlinedIndex, underlinedIndex).getBounds2D().getX(); 671: underlineX2 = x + layout.getLogicalHighlightShape( 672: underlinedIndex + 1, underlinedIndex + 1).getBounds2D().getX(); 673: 674: underline = new Rectangle2D.Double(); 675: if (underlineX1 < underlineX2) 676: { 677: underline.x = underlineX1; 678: underline.width = underlineX2 - underlineX1; 679: } 680: else 681: { 682: underline.x = underlineX2; 683: underline.width = underlineX1 - underlineX2; 684: } 685: 686: 687: underline.height = lineMetrics.getUnderlineThickness(); 688: underline.y = lineMetrics.getUnderlineOffset(); 689: if (underline.y == 0) 690: { 691: /* Some fonts do not specify an underline offset, although they 692: * actually should do so. In that case, the result of calling 693: * lineMetrics.getUnderlineOffset() will be zero. Since it would 694: * look very ugly if the underline was be positioned immediately 695: * below the baseline, we check for this and move the underline 696: * below the descent, as shown in the following ASCII picture: 697: * 698: * ##### ##### # 699: * # # # # 700: * # # # # 701: * # # # # 702: * ##### ###### ---- baseline (0) 703: * # 704: * # 705: * ------------------###----------- lineMetrics.getDescent() 706: */ 707: underline.y = lineMetrics.getDescent(); 708: } 709: 710: underline.y += y; 711: g2.fill(underline); 712: } 713: 714: /** 715: * Draws a rectangle, simulating a dotted stroke by painting only 716: * every second pixel along the one-pixel thick edge. The color of 717: * those pixels is the current color of the Graphics <code>g</code>. 718: * Any other pixels are left unchanged. 719: * 720: * <p><img src="doc-files/BasicGraphicsUtils-7.png" width="360" 721: * height="200" alt="[An illustration that shows which pixels 722: * get painted]" /> 723: * 724: * @param g the graphics into which the rectangle is drawn. 725: * @param x the x coordinate of the rectangle. 726: * @param y the y coordinate of the rectangle. 727: * @param width the width of the rectangle in pixels. 728: * @param height the height of the rectangle in pixels. 729: */ 730: public static void drawDashedRect(Graphics g, 731: int x, int y, int width, int height) 732: { 733: int right = x + width - 1; 734: int bottom = y + height - 1; 735: 736: /* Draw the top and bottom edge of the dotted rectangle. */ 737: for (int i = x; i <= right; i += 2) 738: { 739: g.drawLine(i, y, i, y); 740: g.drawLine(i, bottom, i, bottom); 741: } 742: 743: /* Draw the left and right edge of the dotted rectangle. */ 744: for (int i = y; i <= bottom; i += 2) 745: { 746: g.drawLine(x, i, x, i); 747: g.drawLine(right, i, right, i); 748: } 749: } 750: 751: /** 752: * Determines the preferred width and height of an AbstractButton, 753: * given the gap between the button’s text and icon. 754: * 755: * @param b the button whose preferred size is determined. 756: * 757: * @param textIconGap the gap between the button’s text and 758: * icon. 759: * 760: * @return a <code>Dimension</code> object whose <code>width</code> 761: * and <code>height</code> fields indicate the preferred 762: * extent in pixels. 763: * 764: * @see javax.swing.SwingUtilities#layoutCompoundLabel(JComponent, 765: * FontMetrics, String, Icon, int, int, int, int, Rectangle, Rectangle, 766: * Rectangle, int) 767: */ 768: public static Dimension getPreferredButtonSize(AbstractButton b, 769: int textIconGap) 770: { 771: // These cached rectangles are use here and in BasicButtonUI.paint(), 772: // so these two methods must never be executed concurrently. Maybe 773: // we must use other Rectangle instances here. OTOH, Swing is 774: // designed to be not thread safe, and every layout and paint operation 775: // should be performed from the EventDispatchThread, so it _should_ be 776: // OK to do this optimization. 777: Rectangle viewRect = BasicButtonUI.viewR; 778: viewRect.x = 0; 779: viewRect.y = 0; 780: viewRect.width = Short.MAX_VALUE; 781: viewRect.height = Short.MAX_VALUE; 782: Rectangle iconRect = BasicButtonUI.iconR; 783: iconRect.x = 0; 784: iconRect.y = 0; 785: iconRect.width = 0; 786: iconRect.height = 0; 787: Rectangle textRect = BasicButtonUI.textR; 788: textRect.x = 0; 789: textRect.y = 0; 790: textRect.width = 0; 791: textRect.height = 0; 792: 793: SwingUtilities.layoutCompoundLabel( 794: b, // for the component orientation 795: b.getFontMetrics(b.getFont()), // see comment above 796: b.getText(), 797: b.getIcon(), 798: b.getVerticalAlignment(), 799: b.getHorizontalAlignment(), 800: b.getVerticalTextPosition(), 801: b.getHorizontalTextPosition(), 802: viewRect, iconRect, textRect, 803: textIconGap); 804: 805: /* +------------------------+ +------------------------+ 806: * | | | | 807: * | ICON | | CONTENTCONTENTCONTENT | 808: * | TEXTTEXTTEXT | --> | CONTENTCONTENTCONTENT | 809: * | TEXTTEXTTEXT | | CONTENTCONTENTCONTENT | 810: * +------------------------+ +------------------------+ 811: */ 812: 813: Rectangle contentRect = 814: SwingUtilities.computeUnion(textRect.x, textRect.y, textRect.width, 815: textRect.height, iconRect); 816: 817: Insets insets = b.getInsets(); 818: return new Dimension(insets.left + contentRect.width + insets.right, 819: insets.top + contentRect.height + insets.bottom); 820: } 821: }