summaryrefslogtreecommitdiff
path: root/libjava/classpath/javax/swing/plaf/basic/BasicGraphicsUtils.java
diff options
context:
space:
mode:
Diffstat (limited to 'libjava/classpath/javax/swing/plaf/basic/BasicGraphicsUtils.java')
-rw-r--r--libjava/classpath/javax/swing/plaf/basic/BasicGraphicsUtils.java821
1 files changed, 821 insertions, 0 deletions
diff --git a/libjava/classpath/javax/swing/plaf/basic/BasicGraphicsUtils.java b/libjava/classpath/javax/swing/plaf/basic/BasicGraphicsUtils.java
new file mode 100644
index 000000000..f270d3335
--- /dev/null
+++ b/libjava/classpath/javax/swing/plaf/basic/BasicGraphicsUtils.java
@@ -0,0 +1,821 @@
+/* BasicGraphicsUtils.java
+ Copyright (C) 2003, 2004, 2005 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+package javax.swing.plaf.basic;
+
+import gnu.classpath.SystemProperties;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.FontMetrics;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Insets;
+import java.awt.Rectangle;
+import java.awt.font.FontRenderContext;
+import java.awt.font.LineMetrics;
+import java.awt.font.TextLayout;
+import java.awt.geom.Rectangle2D;
+
+import javax.swing.AbstractButton;
+import javax.swing.Icon;
+import javax.swing.JComponent;
+import javax.swing.SwingUtilities;
+
+
+/**
+ * A utility class providing commonly used drawing and measurement
+ * routines.
+ *
+ * @author Sascha Brawer (brawer@dandelis.ch)
+ */
+public class BasicGraphicsUtils
+{
+ /**
+ * Used as a key for a client property to store cached TextLayouts in. This
+ * is used for speed-up drawing of text in
+ * {@link #drawString(Graphics, String, int, int, int)}.
+ */
+ static final String CACHED_TEXT_LAYOUT =
+ "BasicGraphicsUtils.cachedTextLayout";
+
+ /**
+ * Constructor. It is utterly unclear why this class should
+ * be constructable, but this is what the API specification
+ * says.
+ */
+ public BasicGraphicsUtils()
+ {
+ // Nothing to do here.
+ }
+
+
+ /**
+ * Draws a rectangle that appears etched into the surface, given
+ * four colors that are used for drawing.
+ *
+ * <p><img src="doc-files/BasicGraphicsUtils-1.png" width="360"
+ * height="200" alt="[An illustration that shows which pixels
+ * get painted in what color]" />
+ *
+ * @param g the graphics into which the rectangle is drawn.
+ * @param x the x coordinate of the rectangle.
+ * @param y the y coordinate of the rectangle.
+ * @param width the width of the rectangle in pixels.
+ * @param height the height of the rectangle in pixels.
+ *
+ * @param shadow the color that will be used for painting
+ * the outer side of the top and left edges.
+ *
+ * @param darkShadow the color that will be used for painting
+ * the inner side of the top and left edges.
+ *
+ * @param highlight the color that will be used for painting
+ * the inner side of the bottom and right edges.
+ *
+ * @param lightHighlight the color that will be used for painting
+ * the outer side of the bottom and right edges.
+ *
+ * @see #getEtchedInsets()
+ * @see javax.swing.border.EtchedBorder
+ */
+ public static void drawEtchedRect(Graphics g,
+ int x, int y, int width, int height,
+ Color shadow, Color darkShadow,
+ Color highlight, Color lightHighlight)
+ {
+ Color oldColor;
+ int x2, y2;
+
+ oldColor = g.getColor();
+ x2 = x + width - 1;
+ y2 = y + height - 1;
+
+ try
+ {
+ /* To understand this code, it might be helpful to look at the
+ * image "BasicGraphicsUtils-1.png" that is included with the
+ * JavaDoc. The file is located in the "doc-files" subdirectory.
+ *
+ * (x2, y2) is the coordinate of the most right and bottom pixel
+ * to be painted.
+ */
+ g.setColor(shadow);
+ g.drawLine(x, y, x2 - 1, y); // top, outer
+ g.drawLine(x, y + 1, x, y2 - 1); // left, outer
+
+ g.setColor(darkShadow);
+ g.drawLine(x + 1, y + 1, x2 - 2, y + 1); // top, inner
+ g.drawLine(x + 1, y + 2, x + 1, y2 - 2); // left, inner
+
+ g.setColor(highlight);
+ g.drawLine(x + 1, y2 - 1, x2 - 1, y2 - 1); // bottom, inner
+ g.drawLine(x2 - 1, y + 1, x2 - 1, y2 - 2); // right, inner
+
+ g.setColor(lightHighlight);
+ g.drawLine(x, y2, x2, y2); // bottom, outer
+ g.drawLine(x2, y, x2, y2 - 1); // right, outer
+ }
+ finally
+ {
+ g.setColor(oldColor);
+ }
+ }
+
+
+ /**
+ * Determines the width of the border that gets painted by
+ * {@link #drawEtchedRect}.
+ *
+ * @return an <code>Insets</code> object whose <code>top</code>,
+ * <code>left</code>, <code>bottom</code> and
+ * <code>right</code> field contain the border width at the
+ * respective edge in pixels.
+ */
+ public static Insets getEtchedInsets()
+ {
+ return new Insets(2, 2, 2, 2);
+ }
+
+
+ /**
+ * Draws a rectangle that appears etched into the surface, given
+ * two colors that are used for drawing.
+ *
+ * <p><img src="doc-files/BasicGraphicsUtils-2.png" width="360"
+ * height="200" alt="[An illustration that shows which pixels
+ * get painted in what color]" />
+ *
+ * @param g the graphics into which the rectangle is drawn.
+ * @param x the x coordinate of the rectangle.
+ * @param y the y coordinate of the rectangle.
+ * @param width the width of the rectangle in pixels.
+ * @param height the height of the rectangle in pixels.
+ *
+ * @param shadow the color that will be used for painting the outer
+ * side of the top and left edges, and for the inner side of
+ * the bottom and right ones.
+ *
+ * @param highlight the color that will be used for painting the
+ * inner side of the top and left edges, and for the outer
+ * side of the bottom and right ones.
+ *
+ * @see #getGrooveInsets()
+ * @see javax.swing.border.EtchedBorder
+ */
+ public static void drawGroove(Graphics g,
+ int x, int y, int width, int height,
+ Color shadow, Color highlight)
+ {
+ /* To understand this, it might be helpful to look at the image
+ * "BasicGraphicsUtils-2.png" that is included with the JavaDoc,
+ * and to compare it with "BasicGraphicsUtils-1.png" which shows
+ * the pixels painted by drawEtchedRect. These image files are
+ * located in the "doc-files" subdirectory.
+ */
+ drawEtchedRect(g, x, y, width, height,
+ /* outer topLeft */ shadow,
+ /* inner topLeft */ highlight,
+ /* inner bottomRight */ shadow,
+ /* outer bottomRight */ highlight);
+ }
+
+
+ /**
+ * Determines the width of the border that gets painted by
+ * {@link #drawGroove}.
+ *
+ * @return an <code>Insets</code> object whose <code>top</code>,
+ * <code>left</code>, <code>bottom</code> and
+ * <code>right</code> field contain the border width at the
+ * respective edge in pixels.
+ */
+ public static Insets getGrooveInsets()
+ {
+ return new Insets(2, 2, 2, 2);
+ }
+
+
+ /**
+ * Draws a border that is suitable for buttons of the Basic look and
+ * feel.
+ *
+ * <p><img src="doc-files/BasicGraphicsUtils-3.png" width="500"
+ * height="300" alt="[An illustration that shows which pixels
+ * get painted in what color]" />
+ *
+ * @param g the graphics into which the rectangle is drawn.
+ * @param x the x coordinate of the rectangle.
+ * @param y the y coordinate of the rectangle.
+ * @param width the width of the rectangle in pixels.
+ * @param height the height of the rectangle in pixels.
+ *
+ * @param isPressed <code>true</code> to draw the button border
+ * with a pressed-in appearance; <code>false</code> for
+ * normal (unpressed) appearance.
+ *
+ * @param isDefault <code>true</code> to draw the border with
+ * the appearance it has when hitting the enter key in a
+ * dialog will simulate a click to this button;
+ * <code>false</code> for normal appearance.
+ *
+ * @param shadow the shadow color.
+ * @param darkShadow a darker variant of the shadow color.
+ * @param highlight the highlight color.
+ * @param lightHighlight a brighter variant of the highlight color.
+ */
+ public static void drawBezel(Graphics g,
+ int x, int y, int width, int height,
+ boolean isPressed, boolean isDefault,
+ Color shadow, Color darkShadow,
+ Color highlight, Color lightHighlight)
+ {
+ Color oldColor = g.getColor();
+
+ /* To understand this, it might be helpful to look at the image
+ * "BasicGraphicsUtils-3.png" that is included with the JavaDoc,
+ * and to compare it with "BasicGraphicsUtils-1.png" which shows
+ * the pixels painted by drawEtchedRect. These image files are
+ * located in the "doc-files" subdirectory.
+ */
+ try
+ {
+ if ((isPressed == false) && (isDefault == false))
+ {
+ drawEtchedRect(g, x, y, width, height,
+ lightHighlight, highlight,
+ shadow, darkShadow);
+ }
+
+ if ((isPressed == true) && (isDefault == false))
+ {
+ g.setColor(shadow);
+ g.drawRect(x + 1, y + 1, width - 2, height - 2);
+ }
+
+ if ((isPressed == false) && (isDefault == true))
+ {
+ g.setColor(darkShadow);
+ g.drawRect(x, y, width - 1, height - 1);
+ drawEtchedRect(g, x + 1, y + 1, width - 2, height - 2,
+ lightHighlight, highlight,
+ shadow, darkShadow);
+ }
+
+ if ((isPressed == true) && (isDefault == true))
+ {
+ g.setColor(darkShadow);
+ g.drawRect(x, y, width - 1, height - 1);
+ g.setColor(shadow);
+ g.drawRect(x + 1, y + 1, width - 3, height - 3);
+ }
+ }
+ finally
+ {
+ g.setColor(oldColor);
+ }
+ }
+
+
+ /**
+ * Draws a rectangle that appears lowered into the surface, given
+ * four colors that are used for drawing.
+ *
+ * <p><img src="doc-files/BasicGraphicsUtils-4.png" width="360"
+ * height="200" alt="[An illustration that shows which pixels
+ * get painted in what color]" />
+ *
+ * <p><strong>Compatibility with the Sun reference
+ * implementation:</strong> The Sun reference implementation seems
+ * to ignore the <code>x</code> and <code>y</code> arguments, at
+ * least in JDK 1.3.1 and 1.4.1_01. The method always draws the
+ * rectangular area at location (0, 0). A bug report has been filed
+ * with Sun; its &#x201c;bug ID&#x201d; is 4880003. The GNU Classpath
+ * implementation behaves correctly, thus not replicating this bug.
+ *
+ * @param g the graphics into which the rectangle is drawn.
+ * @param x the x coordinate of the rectangle.
+ * @param y the y coordinate of the rectangle.
+ * @param width the width of the rectangle in pixels.
+ * @param height the height of the rectangle in pixels.
+ *
+ * @param shadow the color that will be used for painting
+ * the inner side of the top and left edges.
+ *
+ * @param darkShadow the color that will be used for painting
+ * the outer side of the top and left edges.
+ *
+ * @param highlight the color that will be used for painting
+ * the inner side of the bottom and right edges.
+ *
+ * @param lightHighlight the color that will be used for painting
+ * the outer side of the bottom and right edges.
+ */
+ public static void drawLoweredBezel(Graphics g,
+ int x, int y, int width, int height,
+ Color shadow, Color darkShadow,
+ Color highlight, Color lightHighlight)
+ {
+ /* Like drawEtchedRect, but swapping darkShadow and shadow.
+ *
+ * To understand this, it might be helpful to look at the image
+ * "BasicGraphicsUtils-4.png" that is included with the JavaDoc,
+ * and to compare it with "BasicGraphicsUtils-1.png" which shows
+ * the pixels painted by drawEtchedRect. These image files are
+ * located in the "doc-files" subdirectory.
+ */
+ drawEtchedRect(g, x, y, width, height,
+ darkShadow, shadow,
+ highlight, lightHighlight);
+ }
+
+
+ /**
+ * Draws a String at the given location, underlining the first
+ * occurence of a specified character. The algorithm for determining
+ * the underlined position is not sensitive to case. If the
+ * character is not part of <code>text</code>, the text will be
+ * drawn without underlining. Drawing is performed in the current
+ * color and font of <code>g</code>.
+ *
+ * <p><img src="doc-files/BasicGraphicsUtils-5.png" width="500"
+ * height="100" alt="[An illustration showing how to use the
+ * method]" />
+ *
+ * @param g the graphics into which the String is drawn.
+ *
+ * @param text the String to draw.
+ *
+ * @param underlinedChar the character whose first occurence in
+ * <code>text</code> will be underlined. It is not clear
+ * why the API specification declares this argument to be
+ * of type <code>int</code> instead of <code>char</code>.
+ * While this would allow to pass Unicode characters outside
+ * Basic Multilingual Plane 0 (U+0000 .. U+FFFE), at least
+ * the GNU Classpath implementation does not underline
+ * anything if <code>underlinedChar</code> is outside
+ * the range of <code>char</code>.
+ *
+ * @param x the x coordinate of the text, as it would be passed to
+ * {@link java.awt.Graphics#drawString(java.lang.String,
+ * int, int)}.
+ *
+ * @param y the y coordinate of the text, as it would be passed to
+ * {@link java.awt.Graphics#drawString(java.lang.String,
+ * int, int)}.
+ */
+ public static void drawString(Graphics g, String text,
+ int underlinedChar, int x, int y)
+ {
+ int index = -1;
+
+ /* It is intentional that lower case is used. In some languages,
+ * the set of lowercase characters is larger than the set of
+ * uppercase ones. Therefore, it is good practice to use lowercase
+ * for such comparisons (which really means that the author of this
+ * code can vaguely remember having read some Unicode techreport
+ * with this recommendation, but is too lazy to look for the URL).
+ */
+ if ((underlinedChar >= 0) || (underlinedChar <= 0xffff))
+ index = text.toLowerCase().indexOf(
+ Character.toLowerCase((char) underlinedChar));
+
+ drawStringUnderlineCharAt(g, text, index, x, y);
+ }
+
+
+ /**
+ * Draws a String at the given location, underlining the character
+ * at the specified index. Drawing is performed in the current color
+ * and font of <code>g</code>.
+ *
+ * <p><img src="doc-files/BasicGraphicsUtils-5.png" width="500"
+ * height="100" alt="[An illustration showing how to use the
+ * method]" />
+ *
+ * @param g the graphics into which the String is drawn.
+ *
+ * @param text the String to draw.
+ *
+ * @param underlinedIndex the index of the underlined character in
+ * <code>text</code>. If <code>underlinedIndex</code> falls
+ * outside the range <code>[0, text.length() - 1]</code>, the
+ * text will be drawn without underlining anything.
+ *
+ * @param x the x coordinate of the text, as it would be passed to
+ * {@link java.awt.Graphics#drawString(java.lang.String,
+ * int, int)}.
+ *
+ * @param y the y coordinate of the text, as it would be passed to
+ * {@link java.awt.Graphics#drawString(java.lang.String,
+ * int, int)}.
+ *
+ * @since 1.4
+ */
+ public static void drawStringUnderlineCharAt(Graphics g, String text,
+ int underlinedIndex,
+ int x, int y)
+ {
+ Graphics2D g2;
+ Rectangle2D.Double underline;
+ FontRenderContext frc;
+ FontMetrics fmet;
+ LineMetrics lineMetrics;
+ Font font;
+ TextLayout layout;
+ double underlineX1, underlineX2;
+ boolean drawUnderline;
+ int textLength;
+
+ textLength = text.length();
+ if (textLength == 0)
+ return;
+
+ drawUnderline = (underlinedIndex >= 0) && (underlinedIndex < textLength);
+
+ // FIXME: unfortunately pango and cairo can't agree on metrics
+ // so for the time being we continue to *not* use TextLayouts.
+ if (true || !(g instanceof Graphics2D))
+ {
+ /* Fall-back. This is likely to produce garbage for any text
+ * containing right-to-left (Hebrew or Arabic) characters, even
+ * if the underlined character is left-to-right.
+ */
+ g.drawString(text, x, y);
+ if (drawUnderline)
+ {
+ fmet = g.getFontMetrics();
+ g.fillRect(
+ /* x */ x + fmet.stringWidth(text.substring(0, underlinedIndex)),
+ /* y */ y + fmet.getDescent() - 1,
+ /* width */ fmet.charWidth(text.charAt(underlinedIndex)),
+ /* height */ 1);
+ }
+
+ return;
+ }
+
+ g2 = (Graphics2D) g;
+ font = g2.getFont();
+ frc = g2.getFontRenderContext();
+ lineMetrics = font.getLineMetrics(text, frc);
+ layout = new TextLayout(text, font, frc);
+
+ /* Draw the text. */
+ layout.draw(g2, x, y);
+ if (!drawUnderline)
+ return;
+
+ underlineX1 = x + layout.getLogicalHighlightShape(
+ underlinedIndex, underlinedIndex).getBounds2D().getX();
+ underlineX2 = x + layout.getLogicalHighlightShape(
+ underlinedIndex + 1, underlinedIndex + 1).getBounds2D().getX();
+
+ underline = new Rectangle2D.Double();
+ if (underlineX1 < underlineX2)
+ {
+ underline.x = underlineX1;
+ underline.width = underlineX2 - underlineX1;
+ }
+ else
+ {
+ underline.x = underlineX2;
+ underline.width = underlineX1 - underlineX2;
+ }
+
+
+ underline.height = lineMetrics.getUnderlineThickness();
+ underline.y = lineMetrics.getUnderlineOffset();
+ if (underline.y == 0)
+ {
+ /* Some fonts do not specify an underline offset, although they
+ * actually should do so. In that case, the result of calling
+ * lineMetrics.getUnderlineOffset() will be zero. Since it would
+ * look very ugly if the underline was be positioned immediately
+ * below the baseline, we check for this and move the underline
+ * below the descent, as shown in the following ASCII picture:
+ *
+ * ##### ##### #
+ * # # # #
+ * # # # #
+ * # # # #
+ * ##### ###### ---- baseline (0)
+ * #
+ * #
+ * ------------------###----------- lineMetrics.getDescent()
+ */
+ underline.y = lineMetrics.getDescent();
+ }
+
+ underline.y += y;
+ g2.fill(underline);
+ }
+
+ /**
+ * Draws a string on the specified component.
+ *
+ * @param c the component
+ * @param g the Graphics context
+ * @param text the string
+ * @param underlinedChar the character to be underlined
+ * @param x the X location
+ * @param y the Y location
+ */
+ static void drawString(JComponent c, Graphics g, String text,
+ int underlinedChar, int x, int y)
+ {
+ int index = -1;
+
+ /* It is intentional that lower case is used. In some languages,
+ * the set of lowercase characters is larger than the set of
+ * uppercase ones. Therefore, it is good practice to use lowercase
+ * for such comparisons (which really means that the author of this
+ * code can vaguely remember having read some Unicode techreport
+ * with this recommendation, but is too lazy to look for the URL).
+ */
+ if ((underlinedChar >= 0) || (underlinedChar <= 0xffff))
+ index = text.toLowerCase().indexOf(
+ Character.toLowerCase((char) underlinedChar));
+
+ drawStringUnderlineCharAt(c, g, text, index, x, y);
+ }
+
+
+ /**
+ * Draws a String at the given location, underlining the character
+ * at the specified index. Drawing is performed in the current color
+ * and font of <code>g</code>.
+ *
+ * <p><img src="doc-files/BasicGraphicsUtils-5.png" width="500"
+ * height="100" alt="[An illustration showing how to use the
+ * method]" />
+ *
+ * This is an accelerated version of the method with the same name. It
+ * uses a pre-laid out TextLayout stored in a client property.
+ *
+ * @param c the component that is drawn
+ * @param g the graphics into which the String is drawn.
+ *
+ * @param text the String to draw.
+ *
+ * @param underlinedIndex the index of the underlined character in
+ * <code>text</code>. If <code>underlinedIndex</code> falls
+ * outside the range <code>[0, text.length() - 1]</code>, the
+ * text will be drawn without underlining anything.
+ *
+ * @param x the x coordinate of the text, as it would be passed to
+ * {@link java.awt.Graphics#drawString(java.lang.String,
+ * int, int)}.
+ *
+ * @param y the y coordinate of the text, as it would be passed to
+ * {@link java.awt.Graphics#drawString(java.lang.String,
+ * int, int)}.
+ */
+ static void drawStringUnderlineCharAt(JComponent c, Graphics g, String text,
+ int underlinedIndex,
+ int x, int y)
+ {
+ Graphics2D g2;
+ Rectangle2D.Double underline;
+ FontRenderContext frc;
+ FontMetrics fmet;
+ LineMetrics lineMetrics;
+ Font font;
+ TextLayout layout;
+ double underlineX1, underlineX2;
+ boolean drawUnderline;
+ int textLength;
+
+ textLength = text.length();
+ if (textLength == 0)
+ return;
+
+ drawUnderline = (underlinedIndex >= 0) && (underlinedIndex < textLength);
+
+ // FIXME: unfortunately pango and cairo can't agree on metrics
+ // so for the time being we continue to *not* use TextLayouts.
+ if (!(g instanceof Graphics2D)
+ || SystemProperties.getProperty("gnu.javax.swing.noGraphics2D") != null)
+ {
+ /* Fall-back. This is likely to produce garbage for any text
+ * containing right-to-left (Hebrew or Arabic) characters, even
+ * if the underlined character is left-to-right.
+ */
+ g.drawString(text, x, y);
+ if (drawUnderline)
+ {
+ fmet = g.getFontMetrics();
+ g.fillRect(
+ /* x */ x + fmet.stringWidth(text.substring(0, underlinedIndex)),
+ /* y */ y + 1,
+ /* width */ fmet.charWidth(text.charAt(underlinedIndex)),
+ /* height */ 1);
+ }
+
+ return;
+ }
+
+ g2 = (Graphics2D) g;
+ font = g2.getFont();
+ frc = g2.getFontRenderContext();
+ lineMetrics = font.getLineMetrics(text, frc);
+ layout = (TextLayout) c.getClientProperty(CACHED_TEXT_LAYOUT);
+ if (layout == null)
+ {
+ layout = new TextLayout(text, font, frc);
+ System.err.println("Unable to use cached TextLayout for: " + text);
+ }
+
+ /* Draw the text. */
+ layout.draw(g2, x, y);
+ if (!drawUnderline)
+ return;
+
+ underlineX1 = x + layout.getLogicalHighlightShape(
+ underlinedIndex, underlinedIndex).getBounds2D().getX();
+ underlineX2 = x + layout.getLogicalHighlightShape(
+ underlinedIndex + 1, underlinedIndex + 1).getBounds2D().getX();
+
+ underline = new Rectangle2D.Double();
+ if (underlineX1 < underlineX2)
+ {
+ underline.x = underlineX1;
+ underline.width = underlineX2 - underlineX1;
+ }
+ else
+ {
+ underline.x = underlineX2;
+ underline.width = underlineX1 - underlineX2;
+ }
+
+
+ underline.height = lineMetrics.getUnderlineThickness();
+ underline.y = lineMetrics.getUnderlineOffset();
+ if (underline.y == 0)
+ {
+ /* Some fonts do not specify an underline offset, although they
+ * actually should do so. In that case, the result of calling
+ * lineMetrics.getUnderlineOffset() will be zero. Since it would
+ * look very ugly if the underline was be positioned immediately
+ * below the baseline, we check for this and move the underline
+ * below the descent, as shown in the following ASCII picture:
+ *
+ * ##### ##### #
+ * # # # #
+ * # # # #
+ * # # # #
+ * ##### ###### ---- baseline (0)
+ * #
+ * #
+ * ------------------###----------- lineMetrics.getDescent()
+ */
+ underline.y = lineMetrics.getDescent();
+ }
+
+ underline.y += y;
+ g2.fill(underline);
+ }
+
+ /**
+ * Draws a rectangle, simulating a dotted stroke by painting only
+ * every second pixel along the one-pixel thick edge. The color of
+ * those pixels is the current color of the Graphics <code>g</code>.
+ * Any other pixels are left unchanged.
+ *
+ * <p><img src="doc-files/BasicGraphicsUtils-7.png" width="360"
+ * height="200" alt="[An illustration that shows which pixels
+ * get painted]" />
+ *
+ * @param g the graphics into which the rectangle is drawn.
+ * @param x the x coordinate of the rectangle.
+ * @param y the y coordinate of the rectangle.
+ * @param width the width of the rectangle in pixels.
+ * @param height the height of the rectangle in pixels.
+ */
+ public static void drawDashedRect(Graphics g,
+ int x, int y, int width, int height)
+ {
+ int right = x + width - 1;
+ int bottom = y + height - 1;
+
+ /* Draw the top and bottom edge of the dotted rectangle. */
+ for (int i = x; i <= right; i += 2)
+ {
+ g.drawLine(i, y, i, y);
+ g.drawLine(i, bottom, i, bottom);
+ }
+
+ /* Draw the left and right edge of the dotted rectangle. */
+ for (int i = y; i <= bottom; i += 2)
+ {
+ g.drawLine(x, i, x, i);
+ g.drawLine(right, i, right, i);
+ }
+ }
+
+ /**
+ * Determines the preferred width and height of an AbstractButton,
+ * given the gap between the button&#x2019;s text and icon.
+ *
+ * @param b the button whose preferred size is determined.
+ *
+ * @param textIconGap the gap between the button&#x2019;s text and
+ * icon.
+ *
+ * @return a <code>Dimension</code> object whose <code>width</code>
+ * and <code>height</code> fields indicate the preferred
+ * extent in pixels.
+ *
+ * @see javax.swing.SwingUtilities#layoutCompoundLabel(JComponent,
+ * FontMetrics, String, Icon, int, int, int, int, Rectangle, Rectangle,
+ * Rectangle, int)
+ */
+ public static Dimension getPreferredButtonSize(AbstractButton b,
+ int textIconGap)
+ {
+ // These cached rectangles are use here and in BasicButtonUI.paint(),
+ // so these two methods must never be executed concurrently. Maybe
+ // we must use other Rectangle instances here. OTOH, Swing is
+ // designed to be not thread safe, and every layout and paint operation
+ // should be performed from the EventDispatchThread, so it _should_ be
+ // OK to do this optimization.
+ Rectangle viewRect = BasicButtonUI.viewR;
+ viewRect.x = 0;
+ viewRect.y = 0;
+ viewRect.width = Short.MAX_VALUE;
+ viewRect.height = Short.MAX_VALUE;
+ Rectangle iconRect = BasicButtonUI.iconR;
+ iconRect.x = 0;
+ iconRect.y = 0;
+ iconRect.width = 0;
+ iconRect.height = 0;
+ Rectangle textRect = BasicButtonUI.textR;
+ textRect.x = 0;
+ textRect.y = 0;
+ textRect.width = 0;
+ textRect.height = 0;
+
+ SwingUtilities.layoutCompoundLabel(
+ b, // for the component orientation
+ b.getFontMetrics(b.getFont()), // see comment above
+ b.getText(),
+ b.getIcon(),
+ b.getVerticalAlignment(),
+ b.getHorizontalAlignment(),
+ b.getVerticalTextPosition(),
+ b.getHorizontalTextPosition(),
+ viewRect, iconRect, textRect,
+ textIconGap);
+
+ /* +------------------------+ +------------------------+
+ * | | | |
+ * | ICON | | CONTENTCONTENTCONTENT |
+ * | TEXTTEXTTEXT | --> | CONTENTCONTENTCONTENT |
+ * | TEXTTEXTTEXT | | CONTENTCONTENTCONTENT |
+ * +------------------------+ +------------------------+
+ */
+
+ Rectangle contentRect =
+ SwingUtilities.computeUnion(textRect.x, textRect.y, textRect.width,
+ textRect.height, iconRect);
+
+ Insets insets = b.getInsets();
+ return new Dimension(insets.left + contentRect.width + insets.right,
+ insets.top + contentRect.height + insets.bottom);
+ }
+}