1 /* BasicGraphicsUtils.java
2 Copyright (C) 2003, 2004, 2005 Free Software Foundation, Inc.
4 This file is part of GNU Classpath.
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)
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.
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
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
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. */
38 package javax.swing.plaf.basic;
40 import gnu.classpath.SystemProperties;
42 import java.awt.Color;
43 import java.awt.Dimension;
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;
55 import javax.swing.AbstractButton;
56 import javax.swing.Icon;
57 import javax.swing.JComponent;
58 import javax.swing.SwingUtilities;
62 * A utility class providing commonly used drawing and measurement
65 * @author Sascha Brawer (brawer@dandelis.ch)
67 public class BasicGraphicsUtils
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)}.
74 static final String CACHED_TEXT_LAYOUT =
75 "BasicGraphicsUtils.cachedTextLayout";
78 * Constructor. It is utterly unclear why this class should
79 * be constructable, but this is what the API specification
82 public BasicGraphicsUtils()
84 // Nothing to do here.
89 * Draws a rectangle that appears etched into the surface, given
90 * four colors that are used for drawing.
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]" />
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.
102 * @param shadow the color that will be used for painting
103 * the outer side of the top and left edges.
105 * @param darkShadow the color that will be used for painting
106 * the inner side of the top and left edges.
108 * @param highlight the color that will be used for painting
109 * the inner side of the bottom and right edges.
111 * @param lightHighlight the color that will be used for painting
112 * the outer side of the bottom and right edges.
114 * @see #getEtchedInsets()
115 * @see javax.swing.border.EtchedBorder
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)
125 oldColor = g.getColor();
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.
135 * (x2, y2) is the coordinate of the most right and bottom pixel
139 g.drawLine(x, y, x2 - 1, y); // top, outer
140 g.drawLine(x, y + 1, x, y2 - 1); // left, outer
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
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
150 g.setColor(lightHighlight);
151 g.drawLine(x, y2, x2, y2); // bottom, outer
152 g.drawLine(x2, y, x2, y2 - 1); // right, outer
156 g.setColor(oldColor);
162 * Determines the width of the border that gets painted by
163 * {@link #drawEtchedRect}.
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.
170 public static Insets getEtchedInsets()
172 return new Insets(2, 2, 2, 2);
177 * Draws a rectangle that appears etched into the surface, given
178 * two colors that are used for drawing.
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]" />
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.
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.
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.
198 * @see #getGrooveInsets()
199 * @see javax.swing.border.EtchedBorder
201 public static void drawGroove(Graphics g,
202 int x, int y, int width, int height,
203 Color shadow, Color highlight)
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.
211 drawEtchedRect(g, x, y, width, height,
212 /* outer topLeft */ shadow,
213 /* inner topLeft */ highlight,
214 /* inner bottomRight */ shadow,
215 /* outer bottomRight */ highlight);
220 * Determines the width of the border that gets painted by
221 * {@link #drawGroove}.
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.
228 public static Insets getGrooveInsets()
230 return new Insets(2, 2, 2, 2);
235 * Draws a border that is suitable for buttons of the Basic look and
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]" />
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.
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.
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.
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.
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)
268 Color oldColor = g.getColor();
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.
278 if ((isPressed == false) && (isDefault == false))
280 drawEtchedRect(g, x, y, width, height,
281 lightHighlight, highlight,
285 if ((isPressed == true) && (isDefault == false))
288 g.drawRect(x + 1, y + 1, width - 2, height - 2);
291 if ((isPressed == false) && (isDefault == true))
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,
300 if ((isPressed == true) && (isDefault == true))
302 g.setColor(darkShadow);
303 g.drawRect(x, y, width - 1, height - 1);
305 g.drawRect(x + 1, y + 1, width - 3, height - 3);
310 g.setColor(oldColor);
316 * Draws a rectangle that appears lowered into the surface, given
317 * four colors that are used for drawing.
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]" />
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.
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.
337 * @param shadow the color that will be used for painting
338 * the inner side of the top and left edges.
340 * @param darkShadow the color that will be used for painting
341 * the outer side of the top and left edges.
343 * @param highlight the color that will be used for painting
344 * the inner side of the bottom and right edges.
346 * @param lightHighlight the color that will be used for painting
347 * the outer side of the bottom and right edges.
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)
354 /* Like drawEtchedRect, but swapping darkShadow and shadow.
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.
362 drawEtchedRect(g, x, y, width, height,
364 highlight, lightHighlight);
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>.
376 * <p><img src="doc-files/BasicGraphicsUtils-5.png" width="500"
377 * height="100" alt="[An illustration showing how to use the
380 * @param g the graphics into which the String is drawn.
382 * @param text the String to draw.
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>.
394 * @param x the x coordinate of the text, as it would be passed to
395 * {@link java.awt.Graphics#drawString(java.lang.String,
398 * @param y the y coordinate of the text, as it would be passed to
399 * {@link java.awt.Graphics#drawString(java.lang.String,
402 public static void drawString(Graphics g, String text,
403 int underlinedChar, int x, int y)
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).
414 if ((underlinedChar >= 0) || (underlinedChar <= 0xffff))
415 index = text.toLowerCase().indexOf(
416 Character.toLowerCase((char) underlinedChar));
418 drawStringUnderlineCharAt(g, text, index, x, y);
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>.
427 * <p><img src="doc-files/BasicGraphicsUtils-5.png" width="500"
428 * height="100" alt="[An illustration showing how to use the
431 * @param g the graphics into which the String is drawn.
433 * @param text the String to draw.
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.
440 * @param x the x coordinate of the text, as it would be passed to
441 * {@link java.awt.Graphics#drawString(java.lang.String,
444 * @param y the y coordinate of the text, as it would be passed to
445 * {@link java.awt.Graphics#drawString(java.lang.String,
450 public static void drawStringUnderlineCharAt(Graphics g, String text,
455 Rectangle2D.Double underline;
456 FontRenderContext frc;
458 LineMetrics lineMetrics;
461 double underlineX1, underlineX2;
462 boolean drawUnderline;
465 textLength = text.length();
469 drawUnderline = (underlinedIndex >= 0) && (underlinedIndex < textLength);
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))
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.
479 g.drawString(text, x, y);
482 fmet = g.getFontMetrics();
484 /* x */ x + fmet.stringWidth(text.substring(0, underlinedIndex)),
485 /* y */ y + fmet.getDescent() - 1,
486 /* width */ fmet.charWidth(text.charAt(underlinedIndex)),
495 frc = g2.getFontRenderContext();
496 lineMetrics = font.getLineMetrics(text, frc);
497 layout = new TextLayout(text, font, frc);
500 layout.draw(g2, x, y);
504 underlineX1 = x + layout.getLogicalHighlightShape(
505 underlinedIndex, underlinedIndex).getBounds2D().getX();
506 underlineX2 = x + layout.getLogicalHighlightShape(
507 underlinedIndex + 1, underlinedIndex + 1).getBounds2D().getX();
509 underline = new Rectangle2D.Double();
510 if (underlineX1 < underlineX2)
512 underline.x = underlineX1;
513 underline.width = underlineX2 - underlineX1;
517 underline.x = underlineX2;
518 underline.width = underlineX1 - underlineX2;
522 underline.height = lineMetrics.getUnderlineThickness();
523 underline.y = lineMetrics.getUnderlineOffset();
524 if (underline.y == 0)
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:
537 * ##### ###### ---- baseline (0)
540 * ------------------###----------- lineMetrics.getDescent()
542 underline.y = lineMetrics.getDescent();
550 * Draws a string on the specified component.
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
559 static void drawString(JComponent c, Graphics g, String text,
560 int underlinedChar, int x, int y)
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).
571 if ((underlinedChar >= 0) || (underlinedChar <= 0xffff))
572 index = text.toLowerCase().indexOf(
573 Character.toLowerCase((char) underlinedChar));
575 drawStringUnderlineCharAt(c, g, text, index, x, y);
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>.
584 * <p><img src="doc-files/BasicGraphicsUtils-5.png" width="500"
585 * height="100" alt="[An illustration showing how to use the
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.
591 * @param c the component that is drawn
592 * @param g the graphics into which the String is drawn.
594 * @param text the String to draw.
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.
601 * @param x the x coordinate of the text, as it would be passed to
602 * {@link java.awt.Graphics#drawString(java.lang.String,
605 * @param y the y coordinate of the text, as it would be passed to
606 * {@link java.awt.Graphics#drawString(java.lang.String,
609 static void drawStringUnderlineCharAt(JComponent c, Graphics g, String text,
614 Rectangle2D.Double underline;
615 FontRenderContext frc;
617 LineMetrics lineMetrics;
620 double underlineX1, underlineX2;
621 boolean drawUnderline;
624 textLength = text.length();
628 drawUnderline = (underlinedIndex >= 0) && (underlinedIndex < textLength);
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)
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.
639 g.drawString(text, x, y);
642 fmet = g.getFontMetrics();
644 /* x */ x + fmet.stringWidth(text.substring(0, underlinedIndex)),
645 /* y */ y + fmet.getDescent() - 1,
646 /* width */ fmet.charWidth(text.charAt(underlinedIndex)),
655 frc = g2.getFontRenderContext();
656 lineMetrics = font.getLineMetrics(text, frc);
657 layout = (TextLayout) c.getClientProperty(CACHED_TEXT_LAYOUT);
660 layout = new TextLayout(text, font, frc);
661 System.err.println("Unable to use cached TextLayout for: " + text);
665 layout.draw(g2, x, y);
669 underlineX1 = x + layout.getLogicalHighlightShape(
670 underlinedIndex, underlinedIndex).getBounds2D().getX();
671 underlineX2 = x + layout.getLogicalHighlightShape(
672 underlinedIndex + 1, underlinedIndex + 1).getBounds2D().getX();
674 underline = new Rectangle2D.Double();
675 if (underlineX1 < underlineX2)
677 underline.x = underlineX1;
678 underline.width = underlineX2 - underlineX1;
682 underline.x = underlineX2;
683 underline.width = underlineX1 - underlineX2;
687 underline.height = lineMetrics.getUnderlineThickness();
688 underline.y = lineMetrics.getUnderlineOffset();
689 if (underline.y == 0)
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:
702 * ##### ###### ---- baseline (0)
705 * ------------------###----------- lineMetrics.getDescent()
707 underline.y = lineMetrics.getDescent();
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.
720 * <p><img src="doc-files/BasicGraphicsUtils-7.png" width="360"
721 * height="200" alt="[An illustration that shows which pixels
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.
730 public static void drawDashedRect(Graphics g,
731 int x, int y, int width, int height)
733 int right = x + width - 1;
734 int bottom = y + height - 1;
736 /* Draw the top and bottom edge of the dotted rectangle. */
737 for (int i = x; i <= right; i += 2)
739 g.drawLine(i, y, i, y);
740 g.drawLine(i, bottom, i, bottom);
743 /* Draw the left and right edge of the dotted rectangle. */
744 for (int i = y; i <= bottom; i += 2)
746 g.drawLine(x, i, x, i);
747 g.drawLine(right, i, right, i);
752 * Determines the preferred width and height of an AbstractButton,
753 * given the gap between the button’s text and icon.
755 * @param b the button whose preferred size is determined.
757 * @param textIconGap the gap between the button’s text and
760 * @return a <code>Dimension</code> object whose <code>width</code>
761 * and <code>height</code> fields indicate the preferred
764 * @see javax.swing.SwingUtilities#layoutCompoundLabel(JComponent,
765 * FontMetrics, String, Icon, int, int, int, int, Rectangle, Rectangle,
768 public static Dimension getPreferredButtonSize(AbstractButton b,
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;
780 viewRect.width = Short.MAX_VALUE;
781 viewRect.height = Short.MAX_VALUE;
782 Rectangle iconRect = BasicButtonUI.iconR;
787 Rectangle textRect = BasicButtonUI.textR;
793 SwingUtilities.layoutCompoundLabel(
794 b, // for the component orientation
795 b.getFontMetrics(b.getFont()), // see comment above
798 b.getVerticalAlignment(),
799 b.getHorizontalAlignment(),
800 b.getVerticalTextPosition(),
801 b.getHorizontalTextPosition(),
802 viewRect, iconRect, textRect,
805 /* +------------------------+ +------------------------+
807 * | ICON | | CONTENTCONTENTCONTENT |
808 * | TEXTTEXTTEXT | --> | CONTENTCONTENTCONTENT |
809 * | TEXTTEXTTEXT | | CONTENTCONTENTCONTENT |
810 * +------------------------+ +------------------------+
813 Rectangle contentRect =
814 SwingUtilities.computeUnion(textRect.x, textRect.y, textRect.width,
815 textRect.height, iconRect);
817 Insets insets = b.getInsets();
818 return new Dimension(insets.left + contentRect.width + insets.right,
819 insets.top + contentRect.height + insets.bottom);