I implemented caching of TextLayouts for all button-like components (Buttons, CheckBoxes/RadioButtons, Menu(Item)s, etc). Should make things a bit faster.
2006-06-21 Roman Kennke <[EMAIL PROTECTED]>
* javax/swing/plaf/basic/BasicButtonListener.java
(propertyChange): Create a TextLayout and store it in the button
when the 'text' property changes.
* javax/swing/plaf/basic/BasicButtonUI.java
(paintText): Call BasicGraphicsUtils utility method for
drawing strings, instead of Graphics.drawString().
* javax/swing/plaf/basic/BasicGraphicsUtils.java
(CACHE_TEXT_LAYOUT): New constant field. Used as a key for
storing
cached text layouts as client properties in JComponents.
(drawString(JComponent,Graphics,String,int,int)): New helper
method.
(drawStringUnderlineCharAt): New helper method.
* javax/swing/plaf/basic/BasicMenuItemUI.java
(PropertyChangeHandler.propertyChange): Update cached text
layout
when 'text' property changes. Use equals() instead of == for
string comparison.
(paintText): Use new BasicGraphicsUtils methods for painting
the cached text layout.
(installListeners): Call super.installListeners() and remove
the unneeded listener installs.
(uninstallListeners): Call super.uninstallListeners() and remove
the unneeded listener uninstalls.
/Roman
--
“Improvement makes straight roads, but the crooked roads, without
Improvement, are roads of Genius.” - William Blake
Index: javax/swing/plaf/basic/BasicButtonListener.java
===================================================================
RCS file: /cvsroot/classpath/classpath/javax/swing/plaf/basic/BasicButtonListener.java,v
retrieving revision 1.13
diff -u -1 -0 -r1.13 BasicButtonListener.java
--- javax/swing/plaf/basic/BasicButtonListener.java 4 May 2006 14:45:50 -0000 1.13
+++ javax/swing/plaf/basic/BasicButtonListener.java 21 Jun 2006 13:10:37 -0000
@@ -34,24 +34,26 @@
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 java.awt.event.ActionEvent;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
-import java.awt.event.InputEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
+import java.awt.font.FontRenderContext;
+import java.awt.font.TextLayout;
+import java.awt.geom.AffineTransform;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.AbstractAction;
import javax.swing.AbstractButton;
import javax.swing.ButtonModel;
import javax.swing.JComponent;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
@@ -59,21 +61,35 @@
public class BasicButtonListener implements MouseListener, MouseMotionListener,
FocusListener, ChangeListener, PropertyChangeListener
{
public BasicButtonListener(AbstractButton b)
{
// Do nothing here.
}
public void propertyChange(PropertyChangeEvent e)
{
- // TODO: What should be done here, if anything?
+ // Store the TextLayout for this in a client property for speed-up
+ // painting of the label.
+ String property = e.getPropertyName();
+ if (property.equals(AbstractButton.TEXT_CHANGED_PROPERTY)
+ || property.equals("font"))
+ {
+ AbstractButton b = (AbstractButton) e.getSource();
+ String text = b.getText();
+ if (text == null)
+ text = "";
+ FontRenderContext frc = new FontRenderContext(new AffineTransform(),
+ false, false);
+ TextLayout layout = new TextLayout(text, b.getFont(), frc);
+ b.putClientProperty(BasicGraphicsUtils.CACHED_TEXT_LAYOUT, layout);
+ }
}
protected void checkOpacity(AbstractButton b)
{
// TODO: What should be done here?
}
public void focusGained(FocusEvent e)
{
if (e.getSource() instanceof AbstractButton)
Index: javax/swing/plaf/basic/BasicButtonUI.java
===================================================================
RCS file: /cvsroot/classpath/classpath/javax/swing/plaf/basic/BasicButtonUI.java,v
retrieving revision 1.38
diff -u -1 -0 -r1.38 BasicButtonUI.java
--- javax/swing/plaf/basic/BasicButtonUI.java 1 Jun 2006 05:17:02 -0000 1.38
+++ javax/swing/plaf/basic/BasicButtonUI.java 21 Jun 2006 13:10:37 -0000
@@ -435,20 +435,24 @@
protected void paintText(Graphics g, AbstractButton b, Rectangle textRect,
String text)
{
Font f = b.getFont();
g.setFont(f);
FontMetrics fm = g.getFontMetrics(f);
if (b.isEnabled())
{
g.setColor(b.getForeground());
- g.drawString(text, textRect.x, textRect.y + fm.getAscent());
+ // FIXME: Underline mnemonic.
+ BasicGraphicsUtils.drawString(b, g, text, -1, textRect.x,
+ textRect.y + fm.getAscent());
}
else
{
String prefix = getPropertyPrefix();
g.setColor(UIManager.getColor(prefix + "disabledText"));
- g.drawString(text, textRect.x, textRect.y + fm.getAscent());
+ // FIXME: Underline mnemonic.
+ BasicGraphicsUtils.drawString(b, g, text, -1, textRect.x,
+ textRect.y + fm.getAscent());
}
}
}
Index: javax/swing/plaf/basic/BasicGraphicsUtils.java
===================================================================
RCS file: /cvsroot/classpath/classpath/javax/swing/plaf/basic/BasicGraphicsUtils.java,v
retrieving revision 1.17
diff -u -1 -0 -r1.17 BasicGraphicsUtils.java
--- javax/swing/plaf/basic/BasicGraphicsUtils.java 18 Oct 2005 22:10:32 -0000 1.17
+++ javax/swing/plaf/basic/BasicGraphicsUtils.java 21 Jun 2006 13:10:38 -0000
@@ -30,20 +30,22 @@
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;
@@ -58,20 +60,28 @@
/**
* A utility class providing commonly used drawing and measurement
* routines.
*
* @author Sascha Brawer ([EMAIL PROTECTED])
*/
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
+ * [EMAIL PROTECTED] #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.
}
@@ -529,20 +539,184 @@
* #
* ------------------###----------- 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
+ * [EMAIL PROTECTED] java.awt.Graphics#drawString(java.lang.String,
+ * int, int)}.
+ *
+ * @param y the y coordinate of the text, as it would be passed to
+ * [EMAIL PROTECTED] 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 + 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 = (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]" />
Index: javax/swing/plaf/basic/BasicMenuItemUI.java
===================================================================
RCS file: /cvsroot/classpath/classpath/javax/swing/plaf/basic/BasicMenuItemUI.java,v
retrieving revision 1.47
diff -u -1 -0 -r1.47 BasicMenuItemUI.java
--- javax/swing/plaf/basic/BasicMenuItemUI.java 13 Jun 2006 09:28:57 -0000 1.47
+++ javax/swing/plaf/basic/BasicMenuItemUI.java 21 Jun 2006 13:10:38 -0000
@@ -45,25 +45,29 @@
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
+import java.awt.font.FontRenderContext;
+import java.awt.font.TextLayout;
+import java.awt.geom.AffineTransform;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import javax.swing.AbstractAction;
+import javax.swing.AbstractButton;
import javax.swing.ActionMap;
import javax.swing.ButtonModel;
import javax.swing.Icon;
import javax.swing.InputMap;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JComponent;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.KeyStroke;
@@ -230,33 +234,47 @@
{
/**
* This method is called when a property of the menuItem is changed.
* Currently it is only used to update the accelerator key bindings.
*
* @param e
* the PropertyChangeEvent
*/
public void propertyChange(PropertyChangeEvent e)
{
- if (e.getPropertyName() == "accelerator")
+ String property = e.getPropertyName();
+ if (property.equals("accelerator"))
{
InputMap map = SwingUtilities.getUIInputMap(menuItem,
JComponent.WHEN_IN_FOCUSED_WINDOW);
if (map != null)
map.remove((KeyStroke) e.getOldValue());
else
map = new ComponentInputMapUIResource(menuItem);
KeyStroke accelerator = (KeyStroke) e.getNewValue();
if (accelerator != null)
map.put(accelerator, "doClick");
}
+ // TextLayout caching for speed-up drawing of text.
+ else if (property.equals(AbstractButton.TEXT_CHANGED_PROPERTY)
+ || property.equals("font"))
+ {
+ AbstractButton b = (AbstractButton) e.getSource();
+ String text = b.getText();
+ if (text == null)
+ text = "";
+ FontRenderContext frc = new FontRenderContext(new AffineTransform(),
+ false, false);
+ TextLayout layout = new TextLayout(text, b.getFont(), frc);
+ b.putClientProperty(BasicGraphicsUtils.CACHED_TEXT_LAYOUT, layout);
+ }
}
}
/**
* A class to handle accelerator keys. This is the Action we will
* perform when the accelerator key for this JMenuItem is pressed.
* @author Anthony Balkissoon abalkiss at redhat dot com
*
*/
class ClickAction extends AbstractAction
@@ -826,26 +844,27 @@
// FIXME: should fix this to use 'disabledForeground', but its
// default value in BasicLookAndFeel is null.
// FIXME: should there be different foreground colours for selected
// or deselected, when disabled?
g.setColor(Color.gray);
int mnemonicIndex = menuItem.getDisplayedMnemonicIndex();
if (mnemonicIndex != -1)
- BasicGraphicsUtils.drawStringUnderlineCharAt(g, text, mnemonicIndex,
+ BasicGraphicsUtils.drawStringUnderlineCharAt(menuItem, g, text,
+ mnemonicIndex,
textRect.x,
textRect.y
+ fm.getAscent());
else
- BasicGraphicsUtils.drawString(g, text, 0, textRect.x,
+ BasicGraphicsUtils.drawString(menuItem, g, text, 0, textRect.x,
textRect.y + fm.getAscent());
}
}
/**
* This method uninstalls the components for this [EMAIL PROTECTED] JMenuItem}.
*
* @param menuItem
* The [EMAIL PROTECTED] JMenuItem} to uninstall components for.
*/
Index: javax/swing/plaf/basic/BasicMenuUI.java
===================================================================
RCS file: /cvsroot/classpath/classpath/javax/swing/plaf/basic/BasicMenuUI.java,v
retrieving revision 1.23
diff -u -1 -0 -r1.23 BasicMenuUI.java
--- javax/swing/plaf/basic/BasicMenuUI.java 17 Apr 2006 07:41:05 -0000 1.23
+++ javax/swing/plaf/basic/BasicMenuUI.java 21 Jun 2006 13:10:38 -0000
@@ -223,24 +223,22 @@
throws NotImplementedException
{
// FIXME: Need to implement
}
/**
* Creates and registers all the listeners for this UI delegate.
*/
protected void installListeners()
{
- ((JMenu) menuItem).addMouseListener(mouseInputListener);
- ((JMenu) menuItem).addMouseMotionListener(mouseInputListener);
+ super.installListeners();
((JMenu) menuItem).addMenuListener(menuListener);
- ((JMenu) menuItem).addMenuDragMouseListener(menuDragMouseListener);
}
protected void setupPostTimer(JMenu menu)
{
// TODO: Implement this properly.
}
/**
* This method uninstalls the defaults and sets any objects created during
* install to null
@@ -269,23 +267,22 @@
{
// FIXME: Need to implement
}
/**
* Unregisters all the listeners that this UI delegate was using. In
* addition, it will also null any listeners that it was using.
*/
protected void uninstallListeners()
{
- ((JMenu) menuItem).removeMouseListener(mouseInputListener);
+ super.uninstallListeners();
((JMenu) menuItem).removeMenuListener(menuListener);
- ((JMenu) menuItem).removePropertyChangeListener(propertyChangeListener);
}
/**
* This class is used by menus to handle mouse events occuring in the
* menu.
*/
protected class MouseInputHandler implements MouseInputListener
{
public void mouseClicked(MouseEvent e)
{
signature.asc
Description: Dies ist ein digital signierter Nachrichtenteil
