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