001/* BasicGraphicsUtils.java 002 Copyright (C) 2003, 2004, 2005 Free Software Foundation, Inc. 003 004This file is part of GNU Classpath. 005 006GNU Classpath is free software; you can redistribute it and/or modify 007it under the terms of the GNU General Public License as published by 008the Free Software Foundation; either version 2, or (at your option) 009any later version. 010 011GNU Classpath is distributed in the hope that it will be useful, but 012WITHOUT ANY WARRANTY; without even the implied warranty of 013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014General Public License for more details. 015 016You should have received a copy of the GNU General Public License 017along with GNU Classpath; see the file COPYING. If not, write to the 018Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 01902110-1301 USA. 020 021Linking this library statically or dynamically with other modules is 022making a combined work based on this library. Thus, the terms and 023conditions of the GNU General Public License cover the whole 024combination. 025 026As a special exception, the copyright holders of this library give you 027permission to link this library with independent modules to produce an 028executable, regardless of the license terms of these independent 029modules, and to copy and distribute the resulting executable under 030terms of your choice, provided that you also meet, for each linked 031independent module, the terms and conditions of the license of that 032module. An independent module is a module which is not derived from 033or based on this library. If you modify this library, you may extend 034this exception to your version of the library, but you are not 035obligated to do so. If you do not wish to do so, delete this 036exception statement from your version. */ 037 038package javax.swing.plaf.basic; 039 040import gnu.classpath.SystemProperties; 041 042import java.awt.Color; 043import java.awt.Dimension; 044import java.awt.Font; 045import java.awt.FontMetrics; 046import java.awt.Graphics; 047import java.awt.Graphics2D; 048import java.awt.Insets; 049import java.awt.Rectangle; 050import java.awt.font.FontRenderContext; 051import java.awt.font.LineMetrics; 052import java.awt.font.TextLayout; 053import java.awt.geom.Rectangle2D; 054 055import javax.swing.AbstractButton; 056import javax.swing.Icon; 057import javax.swing.JComponent; 058import javax.swing.SwingUtilities; 059 060 061/** 062 * A utility class providing commonly used drawing and measurement 063 * routines. 064 * 065 * @author Sascha Brawer (brawer@dandelis.ch) 066 */ 067public class BasicGraphicsUtils 068{ 069 /** 070 * Used as a key for a client property to store cached TextLayouts in. This 071 * is used for speed-up drawing of text in 072 * {@link #drawString(Graphics, String, int, int, int)}. 073 */ 074 static final String CACHED_TEXT_LAYOUT = 075 "BasicGraphicsUtils.cachedTextLayout"; 076 077 /** 078 * Constructor. It is utterly unclear why this class should 079 * be constructable, but this is what the API specification 080 * says. 081 */ 082 public BasicGraphicsUtils() 083 { 084 // Nothing to do here. 085 } 086 087 088 /** 089 * Draws a rectangle that appears etched into the surface, given 090 * four colors that are used for drawing. 091 * 092 * <p><img src="doc-files/BasicGraphicsUtils-1.png" width="360" 093 * height="200" alt="[An illustration that shows which pixels 094 * get painted in what color]" /> 095 * 096 * @param g the graphics into which the rectangle is drawn. 097 * @param x the x coordinate of the rectangle. 098 * @param y the y coordinate of the rectangle. 099 * @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}