diff options
Diffstat (limited to 'libjava/classpath/javax/swing/plaf/basic')
79 files changed, 47351 insertions, 0 deletions
diff --git a/libjava/classpath/javax/swing/plaf/basic/BasicArrowButton.java b/libjava/classpath/javax/swing/plaf/basic/BasicArrowButton.java new file mode 100644 index 000000000..1d6e8874d --- /dev/null +++ b/libjava/classpath/javax/swing/plaf/basic/BasicArrowButton.java @@ -0,0 +1,422 @@ +/* BasicArrowButton.java -- + Copyright (C) 2004, 2005, 2006 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 java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Polygon; + +import javax.swing.ButtonModel; +import javax.swing.JButton; +import javax.swing.SwingConstants; + +/** + * A button that displays an arrow (triangle) that points {@link #NORTH}, + * {@link #SOUTH}, {@link #EAST} or {@link #WEST}. This button is used by + * the {@link BasicComboBoxUI} class. + * + * @see BasicComboBoxUI#createArrowButton + */ +public class BasicArrowButton extends JButton implements SwingConstants +{ + + /** + * The direction that the arrow points. + * + * @see #getDirection() + */ + protected int direction; + + /** + * The color the arrow is painted in if disabled and the bottom and right + * edges of the button. + * This is package-private to avoid an accessor method. + */ + transient Color shadow = Color.GRAY; + + /** + * The color the arrow is painted in if enabled and the bottom and right + * edges of the button. + * This is package-private to avoid an accessor method. + */ + transient Color darkShadow = new Color(102, 102, 102); + + /** + * The top and left edges of the button. + * This is package-private to avoid an accessor method. + */ + transient Color highlight = Color.WHITE; + + /** + * Creates a new <code>BasicArrowButton</code> object with an arrow pointing + * in the specified direction. If the <code>direction</code> is not one of + * the specified constants, no arrow is drawn. + * + * @param direction The direction the arrow points in (one of: + * {@link #NORTH}, {@link #SOUTH}, {@link #EAST} and {@link #WEST}). + */ + public BasicArrowButton(int direction) + { + super(); + setDirection(direction); + setFocusable(false); + } + + /** + * Creates a new BasicArrowButton object with the given colors and + * direction. + * + * @param direction The direction to point in (one of: + * {@link #NORTH}, {@link #SOUTH}, {@link #EAST} and {@link #WEST}). + * @param background The background color. + * @param shadow The shadow color. + * @param darkShadow The dark shadow color. + * @param highlight The highlight color. + */ + public BasicArrowButton(int direction, Color background, Color shadow, + Color darkShadow, Color highlight) + { + this(direction); + setBackground(background); + this.shadow = shadow; + this.darkShadow = darkShadow; + this.highlight = highlight; + setFocusable(false); + } + + /** + * Returns whether the focus can traverse to this component. This method + * always returns <code>false</code>. + * + * @return <code>false</code>. + */ + public boolean isFocusTraversable() + { + return false; + } + + /** + * Returns the direction of the arrow (one of: {@link #NORTH}, + * {@link #SOUTH}, {@link #EAST} and {@link #WEST}). + * + * @return The direction of the arrow. + */ + public int getDirection() + { + return direction; + } + + /** + * Sets the direction of the arrow. + * + * @param dir The new direction of the arrow (one of: {@link #NORTH}, + * {@link #SOUTH}, {@link #EAST} and {@link #WEST}). + */ + public void setDirection(int dir) + { + this.direction = dir; + } + + /** + * Paints the arrow button. The painting is delegated to the + * paintTriangle method. + * + * @param g The Graphics object to paint with. + */ + public void paint(Graphics g) + { + super.paint(g); + + int height = getHeight(); + int size = height / 4; + + int x = (getWidth() - size) / 2; + int y = (height - size) / 2; + + ButtonModel m = getModel(); + if (m.isArmed()) + { + x++; + y++; + } + + paintTriangle(g, x, y, size, direction, isEnabled()); + } + + /** + * Returns the preferred size of the arrow button. + * + * @return The preferred size (always 16 x 16). + */ + public Dimension getPreferredSize() + { + // since Dimension is NOT immutable, we must return a new instance + // every time (if we return a cached value, the caller might modify it) + // - tests show that the reference implementation does the same. + return new Dimension(16, 16); + } + + /** + * Returns the minimum size of the arrow button. + * + * @return The minimum size (always 5 x 5). + */ + public Dimension getMinimumSize() + { + // since Dimension is NOT immutable, we must return a new instance + // every time (if we return a cached value, the caller might modify it) + // - tests show that the reference implementation does the same. + return new Dimension(5, 5); + } + + /** + * Returns the maximum size of the arrow button. + * + * @return The maximum size (always Integer.MAX_VALUE x Integer.MAX_VALUE). + */ + public Dimension getMaximumSize() + { + // since Dimension is NOT immutable, we must return a new instance + // every time (if we return a cached value, the caller might modify it) + // - tests show that the reference implementation does the same. + return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE); + } + + /** + * Paints a triangle with the given size, location and direction. It is + * difficult to explain the rationale behind the positioning of the triangle + * relative to the given (x, y) position - by trial and error we seem to + * match the behaviour of the reference implementation (which is missing a + * specification for this method). + * + * @param g the graphics device. + * @param x the x-coordinate for the triangle's location. + * @param y the y-coordinate for the triangle's location. + * @param size the arrow size (depth). + * @param direction the direction of the arrow (one of: {@link #NORTH}, + * {@link #SOUTH}, {@link #EAST} and {@link #WEST}). + * @param isEnabled if <code>true</code> the arrow is drawn in the enabled + * state, otherwise it is drawn in the disabled state. + */ + public void paintTriangle(Graphics g, int x, int y, int size, int direction, + boolean isEnabled) + { + Color savedColor = g.getColor(); + switch (direction) + { + case NORTH: + paintTriangleNorth(g, x, y, size, isEnabled); + break; + case SOUTH: + paintTriangleSouth(g, x, y, size, isEnabled); + break; + case LEFT: + case WEST: + paintTriangleWest(g, x, y, size, isEnabled); + break; + case RIGHT: + case EAST: + paintTriangleEast(g, x, y, size, isEnabled); + break; + } + g.setColor(savedColor); + } + + /** + * Paints an upward-pointing triangle. This method is called by the + * {@link #paintTriangle(Graphics, int, int, int, int, boolean)} method. + * + * @param g the graphics device. + * @param x the x-coordinate for the anchor point. + * @param y the y-coordinate for the anchor point. + * @param size the arrow size (depth). + * @param isEnabled if <code>true</code> the arrow is drawn in the enabled + * state, otherwise it is drawn in the disabled state. + */ + private void paintTriangleNorth(Graphics g, int x, int y, int size, + boolean isEnabled) + { + int tipX = x + (size - 2) / 2; + int tipY = y; + int baseX1 = tipX - (size - 1); + int baseX2 = tipX + (size - 1); + int baseY = y + (size - 1); + Polygon triangle = new Polygon(); + triangle.addPoint(tipX, tipY); + triangle.addPoint(baseX1, baseY); + triangle.addPoint(baseX2, baseY); + if (isEnabled) + { + g.setColor(Color.DARK_GRAY); + g.fillPolygon(triangle); + g.drawPolygon(triangle); + } + else + { + g.setColor(Color.GRAY); + g.fillPolygon(triangle); + g.drawPolygon(triangle); + g.setColor(Color.WHITE); + g.drawLine(baseX1 + 1, baseY + 1, baseX2 + 1, baseY + 1); + } + } + + /** + * Paints an downward-pointing triangle. This method is called by the + * {@link #paintTriangle(Graphics, int, int, int, int, boolean)} method. + * + * @param g the graphics device. + * @param x the x-coordinate for the anchor point. + * @param y the y-coordinate for the anchor point. + * @param size the arrow size (depth). + * @param isEnabled if <code>true</code> the arrow is drawn in the enabled + * state, otherwise it is drawn in the disabled state. + */ + private void paintTriangleSouth(Graphics g, int x, int y, int size, + boolean isEnabled) + { + int tipX = x + (size - 2) / 2; + int tipY = y + (size - 1); + int baseX1 = tipX - (size - 1); + int baseX2 = tipX + (size - 1); + int baseY = y; + Polygon triangle = new Polygon(); + triangle.addPoint(tipX, tipY); + triangle.addPoint(baseX1, baseY); + triangle.addPoint(baseX2, baseY); + if (isEnabled) + { + g.setColor(Color.DARK_GRAY); + g.fillPolygon(triangle); + g.drawPolygon(triangle); + } + else + { + g.setColor(Color.GRAY); + g.fillPolygon(triangle); + g.drawPolygon(triangle); + g.setColor(Color.WHITE); + g.drawLine(tipX + 1, tipY, baseX2, baseY + 1); + g.drawLine(tipX + 1, tipY + 1, baseX2 + 1, baseY + 1); + } + } + + /** + * Paints a right-pointing triangle. This method is called by the + * {@link #paintTriangle(Graphics, int, int, int, int, boolean)} method. + * + * @param g the graphics device. + * @param x the x-coordinate for the anchor point. + * @param y the y-coordinate for the anchor point. + * @param size the arrow size (depth). + * @param isEnabled if <code>true</code> the arrow is drawn in the enabled + * state, otherwise it is drawn in the disabled state. + */ + private void paintTriangleEast(Graphics g, int x, int y, int size, + boolean isEnabled) + { + int tipX = x + (size - 1); + int tipY = y + (size - 2) / 2; + int baseX = x; + int baseY1 = tipY - (size - 1); + int baseY2 = tipY + (size - 1); + + Polygon triangle = new Polygon(); + triangle.addPoint(tipX, tipY); + triangle.addPoint(baseX, baseY1); + triangle.addPoint(baseX, baseY2); + if (isEnabled) + { + g.setColor(Color.DARK_GRAY); + g.fillPolygon(triangle); + g.drawPolygon(triangle); + } + else + { + g.setColor(Color.GRAY); + g.fillPolygon(triangle); + g.drawPolygon(triangle); + g.setColor(Color.WHITE); + g.drawLine(baseX + 1, baseY2, tipX, tipY + 1); + g.drawLine(baseX + 1, baseY2 + 1, tipX + 1, tipY + 1); + } + } + + /** + * Paints a left-pointing triangle. This method is called by the + * {@link #paintTriangle(Graphics, int, int, int, int, boolean)} method. + * + * @param g the graphics device. + * @param x the x-coordinate for the anchor point. + * @param y the y-coordinate for the anchor point. + * @param size the arrow size (depth). + * @param isEnabled if <code>true</code> the arrow is drawn in the enabled + * state, otherwise it is drawn in the disabled state. + */ + private void paintTriangleWest(Graphics g, int x, int y, int size, + boolean isEnabled) + { + int tipX = x; + int tipY = y + (size - 2) / 2; + int baseX = x + (size - 1); + int baseY1 = tipY - (size - 1); + int baseY2 = tipY + (size - 1); + + Polygon triangle = new Polygon(); + triangle.addPoint(tipX, tipY); + triangle.addPoint(baseX, baseY1); + triangle.addPoint(baseX, baseY2); + if (isEnabled) + { + g.setColor(Color.DARK_GRAY); + g.fillPolygon(triangle); + g.drawPolygon(triangle); + } + else + { + g.setColor(Color.GRAY); + g.fillPolygon(triangle); + g.drawPolygon(triangle); + g.setColor(Color.WHITE); + g.drawLine(baseX + 1, baseY1 + 1, baseX + 1, baseY2 + 1); + } + } + +} diff --git a/libjava/classpath/javax/swing/plaf/basic/BasicBorders.java b/libjava/classpath/javax/swing/plaf/basic/BasicBorders.java new file mode 100644 index 000000000..83afc33a2 --- /dev/null +++ b/libjava/classpath/javax/swing/plaf/basic/BasicBorders.java @@ -0,0 +1,1768 @@ +/* BasicBorders.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 java.awt.Color; +import java.awt.Component; +import java.awt.Graphics; +import java.awt.Insets; +import java.awt.Rectangle; +import java.io.Serializable; + +import javax.swing.AbstractButton; +import javax.swing.ButtonModel; +import javax.swing.JButton; +import javax.swing.JPopupMenu; +import javax.swing.JSplitPane; +import javax.swing.JToolBar; +import javax.swing.UIManager; +import javax.swing.border.AbstractBorder; +import javax.swing.border.BevelBorder; +import javax.swing.border.Border; +import javax.swing.plaf.BorderUIResource; +import javax.swing.plaf.UIResource; +import javax.swing.text.JTextComponent; + +/** + * Provides various borders for the Basic look and feel. + * + * @author Sascha Brawer (brawer@dandelis.ch) + */ +public class BasicBorders +{ + /** + * A MarginBorder that gets shared by multiple components. + * Created on demand by the private helper function {@link + * #getMarginBorder()}. + */ + private static MarginBorder sharedMarginBorder; + + + /** + * Returns a border for drawing push buttons. + * + * <p>The colors of the border are retrieved from the + * <code>UIDefaults</code> of the currently active look and feel + * using the keys <code>“Button.shadow”</code>, + * <code>“Button.darkShadow”</code>, + * <code>“Button.light”</code>, and + * <code>“Button.highlight”</code>. + * + * <p><img src="doc-files/BasicBorders.ButtonBorder-1.png" width="300" + * height="170" alt="[A screen shot of the returned border]" /> + * + * @return a {@link + * javax.swing.plaf.BorderUIResource.CompoundBorderUIResource} + * whose outer border is a {@link ButtonBorder} and whose + * inner border is a {@link MarginBorder}. + */ + public static Border getButtonBorder() + { + Border outer; + + /* The keys for UIDefaults have been determined by writing a + * test program that dumps the UIDefaults to stdout; that program + * was run on a JDK 1.4.1_01 for GNU/Linux. Note that in the API, + * the key "light" is usually called "highlight", and "highlight" + * is usually called "lightHighlight". + */ + outer = new ButtonBorder(UIManager.getColor("Button.shadow"), + UIManager.getColor("Button.darkShadow"), + UIManager.getColor("Button.light"), + UIManager.getColor("Button.highlight")); + + /* While the inner border is shared between multiple buttons, + * we do not share the outer border because ButtonBorders store + * their border colors. We cannot guarantee that the colors + * (which come from UIDefaults) are unchanged between invocations + * of getButtonBorder. We could store the last colors, and share + * the button border if the colors are the same as in the last + * invocation, but it probably is not worth the effort. + */ + return new BorderUIResource.CompoundBorderUIResource( + outer, + /* inner */ getMarginBorder()); + } + + + /** + * Returns a border for drawing radio buttons. + * + * <p>The colors of the border are retrieved from the + * <code>UIDefaults</code> of the currently active look and feel + * using the keys <code>“RadioButton.shadow”</code>, + * <code>“RadioButton.darkShadow”</code>, + * <code>“RadioButton.light”</code>, and + * <code>“RadioButton.highlight”</code>. + * + * <p><img src="doc-files/BasicBorders.RadioButtonBorder-1.png" width="300" + * height="135" alt="[A screen shot of the returned border]" /> + * + * @return a {@link + * javax.swing.plaf.BorderUIResource.CompoundBorderUIResource} + * whose outer border is a {@link RadioButtonBorder} and whose + * inner border is a {@link MarginBorder}. + */ + public static Border getRadioButtonBorder() + { + Border outer; + + /* The keys for UIDefaults have been determined by writing a + * test program that dumps the UIDefaults to stdout; that program + * was run on a JDK 1.4.1_01 for GNU/Linux. Note that in the API, + * the key "light" is usually called "highlight", and "highlight" + * is usually called "lightHighlight". + */ + outer = new RadioButtonBorder( + UIManager.getColor("RadioButton.shadow"), + UIManager.getColor("RadioButton.darkShadow"), + UIManager.getColor("RadioButton.light"), + UIManager.getColor("RadioButton.highlight")); + + /* While the inner border is shared between multiple buttons, we + * do not share the outer border because RadioButtonBorders, being + * ButtonBorders, store their border colors. We cannot guarantee + * that the colors (which come from UIDefaults) are unchanged + * between invocations of getButtonBorder. We could store the last + * colors, and share the button border if the colors are the same + * as in the last invocation, but it probably is not worth the + * effort. + */ + return new BorderUIResource.CompoundBorderUIResource( + outer, + /* inner */ getMarginBorder()); + } + + + /** + * Returns a border for drawing toggle buttons. + * + * <p>The colors of the border are retrieved from the + * <code>UIDefaults</code> of the currently active look and feel + * using the keys <code>“ToggleButton.shadow”</code>, + * <code>“ToggleButton.darkShadow”</code>, + * <code>“ToggleButton.light”</code>, and + * <code>“ToggleButton.highlight”</code>. + * + * <p><img src="doc-files/BasicBorders.ToggleButtonBorder-1.png" width="270" + * height="135" alt="[A screen shot of the returned border]" /> + * + * @return a {@link + * javax.swing.plaf.BorderUIResource.CompoundBorderUIResource} + * whose outer border is a {@link ToggleButtonBorder} and whose + * inner border is a {@link MarginBorder}. + */ + public static Border getToggleButtonBorder() + { + Border outer; + + /* The keys for UIDefaults have been determined by writing a + * test program that dumps the UIDefaults to stdout; that program + * was run on a JDK 1.4.1_01 for GNU/Linux. Note that in the API, + * the key "light" is usually called "highlight", and "highlight" + * is usually called "lightHighlight". + */ + outer = new ToggleButtonBorder( + UIManager.getColor("ToggleButton.shadow"), + UIManager.getColor("ToggleButton.darkShadow"), + UIManager.getColor("ToggleButton.light"), + UIManager.getColor("ToggleButton.highlight")); + + /* While the inner border is shared between multiple buttons, we + * do not share the outer border because ToggleButtonBorders, being + * ButtonBorders, store their border colors. We cannot guarantee + * that the colors (which come from UIDefaults) are unchanged + * between invocations of getButtonBorder. We could store the last + * colors, and share the button border if the colors are the same + * as in the last invocation, but it probably is not worth the + * effort. + */ + return new BorderUIResource.CompoundBorderUIResource( + outer, + /* inner */ getMarginBorder()); + } + + + /** + * Returns a border for drawing a two-pixel thick separator line + * below menu bars. + * + * <p>The colors of the border are retrieved from the + * <code>UIDefaults</code> of the currently active look and feel + * using the keys <code>“MenuBar.shadow”</code> and + * <code>“MenuBar.highlight”</code>. + * + * <p><img src="doc-files/BasicBorders.MenuBarBorder-1.png" width="500" + * height="140" alt="[A screen shot of a JMenuBar with this border]" /> + * + * @return a {@link MenuBarBorder}. + * + * @see javax.swing.JMenuBar + */ + public static Border getMenuBarBorder() + { + /* See comment in methods above for why this border is not shared. */ + return new MenuBarBorder(UIManager.getColor("MenuBar.shadow"), + UIManager.getColor("MenuBar.highlight")); + } + + + /** + * Returns a border for drawing a one-pixel thick border around + * split panes that are interrupted where the divider joins the + * border. + * + * <p>The colors of the border are retrieved from the + * <code>UIDefaults</code> of the currently active look and feel + * using the keys <code>“SplitPane.darkShadow”</code> and + * <code>“SplitPane.highlight”</code>. + * + * <p><img src="doc-files/BasicBorders.SplitPaneBorder-1.png" width="520" + * height="200" alt="[A screen shot for JSplitPane.HORIZONTAL_SPLIT]" /> + * + * <p><img src="doc-files/BasicBorders.SplitPaneBorder-2.png" width="520" + * height="200" alt="[A screen shot for JSplitPane.VERTICAL_SPLIT]" /> + * + * @return a {@link SplitPaneBorder}. + * + * @see javax.swing.JSplitPane + * @see #getSplitPaneDividerBorder() + */ + public static Border getSplitPaneBorder() + { + /* See comment in methods above for why this border is not shared. */ + return new SplitPaneBorder(UIManager.getColor("SplitPane.highlight"), + UIManager.getColor("SplitPane.darkShadow")); + } + + + /** + * Returns a border for drawing a one-pixel thick border around + * the divider of split panes. + * + * <p>The colors of the edges that are adjacent to the child components + * of the <code>JSplitPane</code> are retrieved from the + * <code>UIDefaults</code> of the currently active look and feel + * using the keys <code>“SplitPane.darkShadow”</code> and + * <code>“SplitPane.highlight”</code>. The color of the + * other two edges is the background color of the divider. + * + * <p><img src="doc-files/BasicBorders.SplitPaneDividerBorder-1.png" + * width="520" height="200" alt= + * "[A screen shot for JSplitPane.HORIZONTAL_SPLIT]" /> + * + * @return an instance of <code>SplitPaneDividerBorder</code>, which is + * not a public API class of this package. + * + * @see javax.swing.JSplitPane + * @see javax.swing.plaf.basic.BasicSplitPaneDivider + * @see #getSplitPaneBorder() + * + * @since 1.3 + */ + public static Border getSplitPaneDividerBorder() + { + /* See comment in methods above for why this border is not shared. */ + return new SplitPaneDividerBorder(); + } + + + /** + * Returns a border for drawing a border around a text field + * that makes the field appear as etched into the surface. + * + * <p>The colors of the border are retrieved from the + * <code>UIDefaults</code> of the currently active look and feel + * using the keys <code>“TextField.shadow”</code>, + * <code>“TextField.darkShadow”</code>, + * <code>“TextField.light”</code>, and + * <code>“TextField.highlight”</code>. + * + * <p><img src="doc-files/BasicBorders.FieldBorder-1.png" width="500" + * height="200" alt="[A screen shot of a border returned by + * this method]" /> + * + * @return an instance of {@link FieldBorder}. + * + * @see javax.swing.JTextField + * @see javax.swing.text.JTextComponent + */ + public static Border getTextFieldBorder() + { + /* See comment in methods above for why this border is not shared. */ + return new FieldBorder( + UIManager.getColor("TextField.shadow"), + UIManager.getColor("TextField.darkShadow"), + UIManager.getColor("TextField.light"), + UIManager.getColor("TextField.highlight")); + } + + + /** + * Returns a two-pixel thick, green + * <code>LineBorderUIResource</code>. This is so ugly that look and + * feels better use different borders for their progress bars, or + * they will look really terrible. + * + * <p><img src="doc-files/BasicBorders-1.png" width="120" height="80" + * alt="[A screen shot of a border returned by this method]" /> + */ + public static Border getProgressBarBorder() + { + /* There does not seem to exist a way to parametrize the color + * or thickness of the border through UIDefaults. + */ + return new BorderUIResource.LineBorderUIResource(Color.green, 2); + } + + + /** + * Returns a border that is composed of a raised bevel border and a + * one-pixel thick line border. + * + * <p><img src="doc-files/BasicBorders-2.png" width="300" height="200" + * alt="[A screen shot of a border returned by this method]" /> + * + * <p>The colors of the border are retrieved from the + * <code>UIDefaults</code> of the currently active look and feel + * using the keys <code>“InternalFrame.borderShadow”</code>, + * <code>“InternalFrame.borderDarkShadow”</code>, + * <code>“InternalFrame.borderLight”</code>, + * <code>“InternalFrame.borderHighlight”</code>, and + * (for the inner one-pixel thick line) + * <code>“InternalFrame.borderColor”</code>. + */ + public static Border getInternalFrameBorder() + { + Color shadow, darkShadow, highlight, lightHighlight, line; + + /* See comment in methods above for why this border is not shared. */ + shadow = UIManager.getColor("InternalFrame.borderShadow"); + darkShadow = UIManager.getColor("InternalFrame.borderDarkShadow"); + highlight = UIManager.getColor("InternalFrame.borderLight"); + lightHighlight = UIManager.getColor("InternalFrame.borderHighlight"); + line = UIManager.getColor("InternalFrame.borderColor"); + + return new BorderUIResource.CompoundBorderUIResource( + /* outer border */ + new BorderUIResource.BevelBorderUIResource( + BevelBorder.RAISED, + (highlight != null) ? highlight : Color.lightGray, + (lightHighlight != null) ? lightHighlight : Color.white, + (darkShadow != null) ? darkShadow : Color.black, + (shadow != null) ? shadow : Color.gray), + + /* inner border */ + new BorderUIResource.LineBorderUIResource( + (line != null) ? line : Color.lightGray)); + } + + + /** + * Returns a shared MarginBorder. + */ + static Border getMarginBorder() // intentionally not public + { + /* Swing is not designed to be thread-safe, so there is no + * need to synchronize the access to the global variable. + */ + if (sharedMarginBorder == null) + sharedMarginBorder = new MarginBorder(); + + return sharedMarginBorder; + } + + + /** + * A border whose appearance depends on the state of + * the enclosed button. + * + * <p><img src="doc-files/BasicBorders.ButtonBorder-1.png" width="300" + * height="170" alt="[A screen shot of this border]" /> + * + * @see javax.swing.plaf.basic.BasicGraphicsUtils#drawBezel + * + * @author Sascha Brawer (brawer@dandelis.ch) + */ + public static class ButtonBorder + extends AbstractBorder + implements Serializable, UIResource + { + /** + * Determined using the <code>serialver</code> tool + * of Apple/Sun JDK 1.3.1 on MacOS X 10.1.5. + */ + static final long serialVersionUID = -157053874580739687L; + + + /** + * The color for drawing the shaded parts of the border. + * @see javax.swing.plaf.basic.BasicGraphicsUtils#drawBezel + */ + protected Color shadow; + + + /** + * The color for drawing the dark shaded parts of the border. + * @see javax.swing.plaf.basic.BasicGraphicsUtils#drawBezel + */ + protected Color darkShadow; + + + /** + * The color for drawing the highlighted parts of the border. + * @see javax.swing.plaf.basic.BasicGraphicsUtils#drawBezel + */ + protected Color highlight; + + + /** + * The color for drawing the bright highlighted parts of the border. + * @see javax.swing.plaf.basic.BasicGraphicsUtils#drawBezel + */ + protected Color lightHighlight; + + + /** + * Constructs a new border for drawing a button in the Basic + * look and feel. + * + * @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 ButtonBorder(Color shadow, Color darkShadow, + Color highlight, Color lightHighlight) + { + /* These colors usually come from the UIDefaults of the current + * look and feel. Use fallback values if the colors are not + * supplied. The API specification is silent about what + * behavior is expected for null colors, so users should not + * rely on this fallback (which is why it is not documented in + * the above Javadoc). + */ + this.shadow = (shadow != null) ? shadow : Color.gray; + this.darkShadow = (darkShadow != null) ? darkShadow : Color.black; + this.highlight = (highlight != null) ? highlight : Color.lightGray; + this.lightHighlight = (lightHighlight != null) + ? lightHighlight + : Color.white; + } + + + /** + * Paints the ButtonBorder around a given component. + * + * @param c the component whose border is to be painted. + * @param g the graphics for painting. + * @param x the horizontal position for painting the border. + * @param y the vertical position for painting the border. + * @param width the width of the available area for painting the border. + * @param height the height of the available area for painting the border. + * + * @see javax.swing.plaf.basic.BasicGraphicsUtils#drawBezel + */ + public void paintBorder(Component c, Graphics g, + int x, int y, int width, int height) + { + ButtonModel bmodel = null; + + if (c instanceof AbstractButton) + bmodel = ((AbstractButton) c).getModel(); + + BasicGraphicsUtils.drawBezel( + g, x, y, width, height, + /* pressed */ (bmodel != null) + && /* mouse button pressed */ bmodel.isPressed() + && /* mouse inside */ bmodel.isArmed(), + /* default */ (c instanceof JButton) + && ((JButton) c).isDefaultButton(), + shadow, darkShadow, highlight, lightHighlight); + } + + + /** + * Measures the width of this border. + * + * <p>Although the thickness of the actually painted border + * depends on the state of the enclosed component, this + * measurement always returns the same amount of pixels. Indeed, + * it would be rather confusing if a button was appearing to + * change its size depending on whether it is pressed or not. + * + * @param c the component whose border is to be measured. + * + * @return an Insets object whose <code>left</code>, + * <code>right</code>, <code>top</code> and + * <code>bottom</code> fields indicate the width of the + * border at the respective edge. + * + * @see #getBorderInsets(java.awt.Component, java.awt.Insets) + */ + public Insets getBorderInsets(Component c) + { + /* There is no obvious reason for overriding this method, but we + * try to have exactly the same API as the Sun reference + * implementation. + */ + return getBorderInsets(c, null); + } + + + /** + * Measures the width of this border, storing the results into a + * pre-existing Insets object. + * + * <p>Although the thickness of the actually painted border + * depends on the state of the enclosed component, this + * measurement always returns the same amount of pixels. Indeed, + * it would be rather confusing if a button was appearing to + * change its size depending on whether it is pressed or not. + * + * @param insets an Insets object for holding the result values. + * After invoking this method, the <code>left</code>, + * <code>right</code>, <code>top</code> and + * <code>bottom</code> fields indicate the width of the + * border at the respective edge. + * + * @return the same object that was passed for <code>insets</code>. + * + * @see #getBorderInsets(Component) + */ + public Insets getBorderInsets(Component c, Insets insets) + { + /* The exact amount has been determined using a test program + * that was run on the Sun reference implementation. With + * Apple/Sun JDK 1.3.1 on MacOS X 10.1.5, the result is + * [3, 3, 3, 3]. With Sun JDK 1.4.1_01 on Linux/x86, the + * result is [2, 3, 3, 3]. We use the values from the 1.4.1_01 + * release. + */ + if (insets == null) + return new Insets(2, 3, 3, 3); + + insets.top = 2; + insets.bottom = insets.left = insets.right = 3; + return insets; + } + } + + + /** + * A border that makes its enclosed component appear as lowered + * into the surface. Typically used for text fields. + * + * <p><img src="doc-files/BasicBorders.FieldBorder-1.png" width="500" + * height="200" alt="[A screen shot of this border]" /> + * + * @see javax.swing.plaf.basic.BasicGraphicsUtils#drawEtchedRect + * + * @author Sascha Brawer (brawer@dandelis.ch) + */ + public static class FieldBorder + extends AbstractBorder + implements UIResource + { + /** + * Determined using the <code>serialver</code> tool + * of Apple/Sun JDK 1.3.1 on MacOS X 10.1.5. + */ + static final long serialVersionUID = 949220756998454908L; + + + /** + * The color for drawing the outer half of the top and left + * edges. + */ + protected Color shadow; + + + /** + * The color for drawing the inner half of the top and left + * edges. + */ + protected Color darkShadow; + + + /** + * The color for drawing the inner half of the bottom and right + * edges. + */ + protected Color highlight; + + + /** + * The color for drawing the outer half of the bottom and right + * edges. + */ + protected Color lightHighlight; + + + /** + * Constructs a new border for drawing a text field in the Basic + * look and feel. + * + * @param shadow the color for drawing the outer half + * of the top and left edges. + * + * @param darkShadow the color for drawing the inner half + * of the top and left edges. + * + * @param highlight the color for drawing the inner half + * of the bottom and right edges. + * + * @param lightHighlight the color for drawing the outer half + * of the bottom and right edges. + */ + public FieldBorder(Color shadow, Color darkShadow, + Color highlight, Color lightHighlight) + { + /* These colors usually come from the UIDefaults of the current + * look and feel. Use fallback values if the colors are not + * supplied. The API specification is silent about what + * behavior is expected for null colors, so users should not + * rely on this fallback (which is why it is not documented in + * the above Javadoc). + */ + this.shadow = (shadow != null) ? shadow : Color.gray; + this.darkShadow = (darkShadow != null) ? darkShadow : Color.black; + this.highlight = (highlight != null) ? highlight : Color.lightGray; + this.lightHighlight = (lightHighlight != null) + ? lightHighlight : Color.white; + } + + + /** + * Paints the FieldBorder around a given component. + * + * @param c the component whose border is to be painted. + * @param g the graphics for painting. + * @param x the horizontal position for painting the border. + * @param y the vertical position for painting the border. + * @param width the width of the available area for painting the border. + * @param height the height of the available area for painting the border. + * + * @see javax.swing.plaf.basic.BasicGraphicsUtils#drawEtchedRect + */ + public void paintBorder(Component c, Graphics g, + int x, int y, int width, int height) + { + BasicGraphicsUtils.drawEtchedRect(g, x, y, width, height, + shadow, darkShadow, + highlight, lightHighlight); + } + + + /** + * Measures the width of this border. + * + * @param c the component whose border is to be measured. + * If <code>c</code> is an instance of {@link + * javax.swing.text.JTextComponent}, its margin is + * added to the border size. + * + * @return an Insets object whose <code>left</code>, + * <code>right</code>, <code>top</code> and + * <code>bottom</code> fields indicate the width of the + * border at the respective edge. + * + * @see #getBorderInsets(java.awt.Component, java.awt.Insets) + */ + public Insets getBorderInsets(Component c) + { + return getBorderInsets(c, null); + } + + + /** + * Measures the width of this border, storing the results into a + * pre-existing Insets object. + * + * @param c the component whose border is to be measured. + * If <code>c</code> is an instance of {@link + * javax.swing.text.JTextComponent}, its margin is + * added to the border size. + * + * @param insets an Insets object for holding the result values. + * After invoking this method, the <code>left</code>, + * <code>right</code>, <code>top</code> and + * <code>bottom</code> fields indicate the width of the + * border at the respective edge. + * + * @return the same object that was passed for <code>insets</code>. + * + * @see #getBorderInsets(Component) + */ + public Insets getBorderInsets(Component c, Insets insets) + { + if (insets == null) + insets = new Insets(2, 2, 2, 2); + else + insets.top = insets.left = insets.bottom = insets.right = 2; + + if (c instanceof JTextComponent) + { + Insets margin = ((JTextComponent) c).getMargin(); + insets.top += margin.top; + insets.left += margin.left; + insets.bottom += margin.bottom; + insets.right += margin.right; + } + + return insets; + } + } + + + /** + * An invisible, but spacing border whose margin is determined + * by calling the <code>getMargin()</code> method of the enclosed + * component. If the enclosed component has no such method, + * this border will not occupy any space. + * + * <p><img src="doc-files/BasicBorders.MarginBorder-1.png" width="325" + * height="200" alt="[An illustration that shows how MarginBorder + * determines its borders]" /> + * + * @author Sascha Brawer (brawer@dandelis.ch) + */ + public static class MarginBorder + extends AbstractBorder + implements Serializable, UIResource + { + /** + * Determined using the <code>serialver</code> tool + * of Apple/Sun JDK 1.3.1 on MacOS X 10.1.5. + */ + static final long serialVersionUID = -3035848353448896090L; + + + /** + * Constructs a new MarginBorder. + */ + public MarginBorder() + { + // Nothing to do here. + } + + /** + * Measures the width of this border. + * + * @param c the component whose border is to be measured. + * + * @return an Insets object whose <code>left</code>, <code>right</code>, + * <code>top</code> and <code>bottom</code> fields indicate the + * width of the border at the respective edge. + * + * @see #getBorderInsets(java.awt.Component, java.awt.Insets) + */ + public Insets getBorderInsets(Component c) + { + return getBorderInsets(c, new Insets(0, 0, 0, 0)); + } + + + /** + * Determines the insets of this border by calling the + * <code>getMargin()</code> method of the enclosed component. The + * resulting margin will be stored into the the <code>left</code>, + * <code>right</code>, <code>top</code> and <code>bottom</code> + * fields of the passed <code>insets</code> parameter. + * + * <p>Unfortunately, <code>getMargin()</code> is not a method of + * {@link javax.swing.JComponent} or some other common superclass + * of things with margins. While reflection could be used to + * determine the existence of this method, this would be slow on + * many virtual machines. Therefore, the current implementation + * knows about {@link javax.swing.AbstractButton#getMargin()}, + * {@link javax.swing.JPopupMenu#getMargin()}, {@link + * javax.swing.JToolBar#getMargin()}, and {@link + * javax.swing.text.JTextComponent}. If <code>c</code> is an + * instance of a known class, the respective + * <code>getMargin()</code> method is called to determine the + * correct margin. Otherwise, a zero-width margin is returned. + * + * @param c the component whose border is to be measured. + * + * @return the same object that was passed for <code>insets</code>, + * but with changed fields. + */ + public Insets getBorderInsets(Component c, Insets insets) + { + Insets margin = null; + + /* This is terrible object-oriented design. See the above Javadoc + * for an excuse. + */ + if (c instanceof AbstractButton) + margin = ((AbstractButton) c).getMargin(); + else if (c instanceof JPopupMenu) + margin = ((JPopupMenu) c).getMargin(); + else if (c instanceof JToolBar) + margin = ((JToolBar) c).getMargin(); + else if (c instanceof JTextComponent) + margin = ((JTextComponent) c).getMargin(); + + if (margin == null) + insets.top = insets.left = insets.bottom = insets.right = 0; + else + { + insets.top = margin.top; + insets.left = margin.left; + insets.bottom = margin.bottom; + insets.right = margin.right; + } + + return insets; + } + } + + + /** + * A border for drawing a separator line below JMenuBar. + * + * <p><img src="doc-files/BasicBorders.MenuBarBorder-1.png" width="500" + * height="140" alt="[A screen shot of a JMenuBar with this border]" /> + * + * @author Sascha Brawer (brawer@dandelis.ch) + */ + public static class MenuBarBorder + extends AbstractBorder + implements UIResource + { + /** + * Determined using the <code>serialver</code> tool + * of Apple/Sun JDK 1.3.1 on MacOS X 10.1.5. + */ + static final long serialVersionUID = -6909056571935227506L; + + + /** + * The shadow color, which is used for the upper line of the + * two-pixel thick bottom edge. + */ + private Color shadow; + + + /** + * The highlight color, which is used for the lower line of the + * two-pixel thick bottom edge. + */ + private Color highlight; + + + /** + * Constructs a new MenuBarBorder for drawing a JMenuBar in + * the Basic look and feel. + * + * <p><img src="doc-files/BasicBorders.MenuBarBorder-1.png" width="500" + * height="140" alt="[A screen shot of a JMenuBar with this + * border]" /> + * + * @param shadow the shadow color, which is used for the upper + * line of the two-pixel thick bottom edge. + * + * @param highlight the shadow color, which is used for the lower + * line of the two-pixel thick bottom edge. + */ + public MenuBarBorder(Color shadow, Color highlight) + { + /* These colors usually come from the UIDefaults of the current + * look and feel. Use fallback values if the colors are not + * supplied. The API specification is silent about what + * behavior is expected for null colors, so users should not + * rely on this fallback (which is why it is not documented in + * the above Javadoc). + */ + this.shadow = (shadow != null) ? shadow : Color.gray; + this.highlight = (highlight != null) ? highlight : Color.white; + } + + + /** + * Paints the MenuBarBorder around a given component. + * + * @param c the component whose border is to be painted, usually + * an instance of {@link javax.swing.JMenuBar}. + * + * @param g the graphics for painting. + * @param x the horizontal position for painting the border. + * @param y the vertical position for painting the border. + * @param width the width of the available area for painting the border. + * @param height the height of the available area for painting the border. + */ + public void paintBorder(Component c, Graphics g, + int x, int y, int width, int height) + { + Color oldColor; + + /* To understand this code, it might be helpful to look at the + * image "BasicBorders.MenuBarBorder-1.png" that is included + * with the JavaDoc. It is located in the "doc-files" + * subdirectory. + */ + oldColor = g.getColor(); + y = y + height - 2; + try + { + g.setColor(shadow); + g.drawLine(x, y, x + width - 2, y); + g.drawLine(x, y + 1, x, y + 1); + g.drawLine(x + width - 2, y + 1, x + width - 2, y + 1); + + g.setColor(highlight); + g.drawLine(x + 1, y + 1, x + width - 3, y + 1); + g.drawLine(x + width - 1, y, x + width - 1, y + 1); + } + finally + { + g.setColor(oldColor); + } + } + + + /** + * Measures the width of this border. + * + * @param c the component whose border is to be measured. + * + * @return an Insets object whose <code>left</code>, + * <code>right</code>, <code>top</code> and + * <code>bottom</code> fields indicate the width of the + * border at the respective edge. + * + * @see #getBorderInsets(java.awt.Component, java.awt.Insets) + */ + public Insets getBorderInsets(Component c) + { + /* There is no obvious reason for overriding this method, but we + * try to have exactly the same API as the Sun reference + * implementation. + */ + return getBorderInsets(c, null); + } + + + /** + * Measures the width of this border, storing the results into a + * pre-existing Insets object. + * + * @param insets an Insets object for holding the result values. + * After invoking this method, the <code>left</code>, + * <code>right</code>, <code>top</code> and + * <code>bottom</code> fields indicate the width of the + * border at the respective edge. + * + * @return the same object that was passed for <code>insets</code>. + * + * @see #getBorderInsets(Component) + */ + public Insets getBorderInsets(Component c, Insets insets) + { + /* The exact amount has been determined using a test program + * that was run on the Apple/Sun JDK 1.3.1 on MacOS X, and the + * Sun JDK 1.4.1_01 on GNU/Linux for x86. Both gave [0,0,2,0], + * which was expected from looking at the screen shot. + */ + if (insets == null) + return new Insets(0, 0, 2, 0); + + insets.left = insets.right = insets.top = 0; + insets.bottom = 2; + return insets; + } + } + + + /** + * A border for drawing radio buttons in the Basic look and feel. + * + * <p><img src="doc-files/BasicBorders.RadioButtonBorder-1.png" width="300" + * height="135" alt="[A screen shot of this border]" /> + * + * <p>Note about the screen shot: Normally, the + * <code>borderPainted</code> property is <code>false</code> for + * JRadioButtons. For this screen shot, it has been set to + * <code>true</code> so the borders get drawn. Also, a + * concretization of the Basic look and would typically provide + * icons for the various states of radio buttons. + * + * <p>Note that the focus rectangle is invisible If the radio button + * is currently selected. While it might be debatable whether this + * makes a lot of sense, this behavior can be observed in the Sun + * reference implementation (in JDK 1.3.1 and 1.4.1). The Classpath + * implementation tries to exactly replicate the JDK appearance. + * + * @see javax.swing.plaf.basic.BasicGraphicsUtils#drawBezel + * + * @author Sascha Brawer (brawer@dandelis.ch) + */ + public static class RadioButtonBorder + extends ButtonBorder + { + /** + * Determined using the <code>serialver</code> tool + * of Apple/Sun JDK 1.3.1 on MacOS X 10.1.5. + */ + static final long serialVersionUID = 1596945751743747369L; + + + /** + * Constructs a new border for drawing a JRadioButton in + * the Basic look and feel. + * + * @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 RadioButtonBorder(Color shadow, Color darkShadow, + Color highlight, Color lightHighlight) + { + /* The superclass ButtonBorder substitutes null arguments + * with fallback colors. + */ + super(shadow, darkShadow, highlight, lightHighlight); + } + + + /** + * Paints the RadioButtonBorder around a given component. + * + * <p>The Sun implementation always seems to draw exactly + * the same border, irrespective of the state of the button. + * This is rather surprising, but GNU Classpath emulates the + * observable behavior. + * + * @param c the component whose border is to be painted. + * @param g the graphics for painting. + * @param x the horizontal position for painting the border. + * @param y the vertical position for painting the border. + * @param width the width of the available area for painting the border. + * @param height the height of the available area for painting the border. + * + * @see javax.swing.plaf.basic.BasicGraphicsUtils#drawBezel + */ + public void paintBorder(Component c, Graphics g, + int x, int y, int width, int height) + { + AbstractButton button = null; + ButtonModel bmodel = null; + boolean lowered = false; + boolean focused = false; + + if (c instanceof AbstractButton) + { + button = (AbstractButton) c; + bmodel = button.getModel(); + } + + if (bmodel != null) + { + lowered = button.isSelected() + || (/* mouse inside */ bmodel.isArmed() && bmodel.isPressed()); + focused = button.hasFocus() && button.isFocusPainted(); + } + + if (lowered) + BasicGraphicsUtils.drawLoweredBezel(g, x, y, width, height, + shadow, darkShadow, + highlight, lightHighlight); + else + BasicGraphicsUtils.drawBezel(g, x, y, width, height, + /* isPressed */ false, + /* isPefault */ focused, + shadow, darkShadow, + highlight, lightHighlight); + } + + + /** + * Measures the width of this border. + * + * @param c the component whose border is to be measured. + * + * @return an Insets object whose <code>left</code>, + * <code>right</code>, <code>top</code> and + * <code>bottom</code> fields indicate the width of the + * border at the respective edge. + * + * @see #getBorderInsets(java.awt.Component, java.awt.Insets) + */ + public Insets getBorderInsets(Component c) + { + /* There is no obvious reason for overriding this method, but we + * try to have exactly the same API as the Sun reference + * implementation. + */ + return getBorderInsets(c, null); + } + + + /** + * Measures the width of this border, storing the results into a + * pre-existing Insets object. + * + * @param insets an Insets object for holding the result values. + * After invoking this method, the <code>left</code>, + * <code>right</code>, <code>top</code> and + * <code>bottom</code> fields indicate the width of the + * border at the respective edge. + * + * @return the same object that was passed for <code>insets</code>. + * + * @see #getBorderInsets(Component) + */ + public Insets getBorderInsets(Component c, Insets insets) + { + /* The exact amount has been determined using a test program + * that was run on the Apple/Sun JDK 1.3.1 on MacOS X, and the + * Sun JDK 1.4.1_01 on GNU/Linux for x86. Both gave [2,2,2,2]. + */ + if (insets == null) + return new Insets(2, 2, 2, 2); + + insets.left = insets.right = insets.top = insets.bottom = 2; + return insets; + } + } + + + /** + * A one-pixel thick border for rollover buttons, for example in + * tool bars. + * + * @since 1.4 + * @author Sascha Brawer (brawer@dandelis.ch) + */ + public static class RolloverButtonBorder + extends ButtonBorder + { + /** + * Determined using the <code>serialver</code> tool + * of Sun JDK 1.4.1_01 on GNU/Linux 2.4.20 for x86. + */ + static final long serialVersionUID = 1976364864896996846L; + + + /** + * Constructs a new border for drawing a roll-over button + * in the Basic look and feel. + * + * @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 RolloverButtonBorder(Color shadow, Color darkShadow, + Color highlight, Color lightHighlight) + { + super(shadow, darkShadow, highlight, lightHighlight); + } + + + /** + * Paints the border around a rollover button. If <code>c</code> + * is not an {@link javax.swing.AbstractButton} whose model + * returns <code>true</code> for {@link + * javax.swing.ButtonModel#isRollover}, nothing gets painted at + * all. + * + * @param c the button whose border is to be painted. + * @param g the graphics for painting. + * @param x the horizontal position for painting the border. + * @param y the vertical position for painting the border. + * @param width the width of the available area for painting the border. + * @param height the height of the available area for painting the border. + */ + public void paintBorder(Component c, Graphics g, + int x, int y, int width, int height) + { + ButtonModel bmodel = null; + boolean drawPressed; + Color oldColor = g.getColor(); + int x2, y2; + + if (c instanceof AbstractButton) + bmodel = ((AbstractButton) c).getModel(); + + /* Draw nothing if c is not a rollover button. */ + if ((bmodel == null) || !bmodel.isRollover()) + return; + + /* Draw nothing if the mouse is pressed, but outside the button. */ + if (bmodel.isPressed() && !bmodel.isArmed()) + return; + + drawPressed = bmodel.isSelected() || bmodel.isPressed(); + x2 = x + width - 1; + y2 = y + height - 1; + + try + { + g.setColor(drawPressed ? shadow : lightHighlight); + g.drawLine(x, y, x2 - 1, y); // top edge + g.drawLine(x, y + 1, x, y2 - 1); // left edge + + g.setColor(drawPressed ? lightHighlight : shadow); + g.drawLine(x, y2, x2, y2); // bottom edge + g.drawLine(x2, y, x2, y2 - 1); // right edge + } + finally + { + g.setColor(oldColor); + } + } + } + + + /** + * A border for JSplitPanes in the Basic look and feel. The divider + * in the middle of the JSplitPane has its own border class, of which + * an instance can be obtained with {@link #getSplitPaneDividerBorder()}. + * + * <p><img src="doc-files/BasicBorders.SplitPaneBorder-1.png" width="520" + * height="200" alt="[A screen shot for JSplitPane.HORIZONTAL_SPLIT]" /> + * + * <p><img src="doc-files/BasicBorders.SplitPaneBorder-2.png" width="520" + * height="200" alt="[A screen shot for JSplitPane.VERTICAL_SPLIT]" /> + * + * <p>In contrast to the other borders of the Basic look and feel, + * this class is not serializable. While this might be unintended, + * GNU Classpath follows the specification in order to be fully + * compatible with the Sun reference implementation. + * + * <p>In the Sun JDK, the bottom edge of the divider also gets + * painted if the orientation of the enclosed JSplitPane is + * <code>JSplitPane.VERTICAL_SPLIT</code> (at least in versions + * 1.3.1 and 1.4.1). GNU Classpath does not replicate this bug. A + * report has been filed with Sun (bug ID 4885629). + * + * <p>Note that the bottom left pixel of the border has a different + * color depending on the orientation of the enclosed JSplitPane. + * Although this is visually inconsistent, Classpath replicates the + * appearance of the Sun reference implementation. A bug report has + * been filed with Sun (review ID 188774). + * + * @see #getSplitPaneBorder() + * @see #getSplitPaneDividerBorder() + * + * @author Sascha Brawer (brawer@dandelis.ch) + */ + public static class SplitPaneBorder implements Border, UIResource + { + /** + * Indicates that the top edge shall be not be painted + * by {@link #paintRect}. + */ + private static final int SUPPRESS_TOP = 1; + + + /** + * Indicates that the left edge shall be not be painted + * by {@link #paintRect}. + */ + private static final int SUPPRESS_LEFT = 2; + + + /** + * Indicates that the bottom edge shall be not be painted + * by {@link #paintRect}. + */ + private static final int SUPPRESS_BOTTOM = 4; + + + /** + * Indicates that the right edge shall be not be painted + * by {@link #paintRect}. + */ + private static final int SUPPRESS_RIGHT = 8; + + + /** + * The color for drawing the bottom and right edges of the border. + */ + protected Color highlight; + + + /** + * The color for drawing the top and left edges of the border. + */ + protected Color shadow; + + + /** + * Constructs a new border for drawing a JSplitPane in the Basic + * look and feel. The divider in the middle of the JSplitPane has + * its own border class, <code>SplitPaneDividerBorder</code>. + * + * @param shadow the shadow color. + * @param highlight the highlight color. + */ + public SplitPaneBorder(Color highlight, Color shadow) + { + /* These colors usually come from the UIDefaults of the current + * look and feel. Use fallback values if the colors are not + * supplied. The API specification is silent about what + * behavior is expected for null colors, so users should not + * rely on this fallback (which is why it is not documented in + * the above Javadoc). + */ + this.shadow = (shadow != null) ? shadow : Color.black; + this.highlight = (highlight != null) ? highlight : Color.white; + } + + + /** + * Paints the border around a <code>JSplitPane</code>. + * + * <p><img src="doc-files/BasicBorders.SplitPaneBorder-1.png" width="520" + * height="200" alt="[A screen shot for JSplitPane.HORIZONTAL_SPLIT]" /> + * + * <p><img src="doc-files/BasicBorders.SplitPaneBorder-2.png" width="520" + * height="200" alt="[A screen shot for JSplitPane.VERTICAL_SPLIT]" /> + * + * @param c the <code>JSplitPane</code> whose border is to be painted. + * @param g the graphics for painting. + * @param x the horizontal position for painting the border. + * @param y the vertical position for painting the border. + * @param width the width of the available area for painting the border. + * @param height the height of the available area for painting the border. + */ + public void paintBorder(Component c, Graphics g, + int x, int y, int width, int height) + { + JSplitPane splitPane; + Component content; + + if (!(c instanceof JSplitPane)) + return; + + splitPane = (JSplitPane) c; + switch (splitPane.getOrientation()) + { + case JSplitPane.HORIZONTAL_SPLIT: + if ((content = splitPane.getLeftComponent()) != null) + paintRect(g, SUPPRESS_RIGHT, true, x, y, content.getBounds()); + if ((content = splitPane.getRightComponent()) != null) + paintRect(g, SUPPRESS_LEFT, true, x, y, content.getBounds()); + break; + + case JSplitPane.VERTICAL_SPLIT: + if ((content = splitPane.getTopComponent()) != null) + paintRect(g, SUPPRESS_BOTTOM, false, x, y, content.getBounds()); + if ((content = splitPane.getBottomComponent()) != null) + paintRect(g, SUPPRESS_TOP, false, x, y, content.getBounds()); + break; + } + } + + + /** + * Paints a border around a child of a <code>JSplitPane</code>, + * omitting some of the edges. + * + * @param g the graphics for painting. + * + * @param suppress a bit mask indicating the set of suppressed + * edges, for example <code>SUPPRESS_TOP | SUPPRESS_RIGHT</code>. + * + * @param x the x coordinate of the SplitPaneBorder. + * + * @param y the y coordinate of the SplitPaneBorder. + * + * @param shadeBottomLeftPixel <code>true</code> to paint the + * bottom left pixel in the shadow color, + * <code>false</code> for the highlight color. The Basic + * look and feel uses the highlight color for the bottom + * left pixel of the border of a JSplitPane whose + * orientation is VERTICAL_SPLIT, and the shadow color + * otherwise. While this might be a strange distinction, + * Classpath tries to look identical to the reference + * implementation. A bug report has been filed with Sun; + * its review ID is 188774. We currently replicate the + * Sun behavior. + * + * @param rect the bounds of the child of JSplitPane whose + * border is to be painted. + */ + private void paintRect(Graphics g, int suppress, + boolean shadeBottomLeftPixel, + int x, int y, + Rectangle rect) + { + if (rect == null) + return; + + /* On each edge, the border exceeds the enclosed child by one + * pixel. See the image "BasicBorders.SplitPaneBorder-1.png" in + * the directory "doc-files". + */ + x += rect.x - 1; + y += rect.y - 1; + int right = x + rect.width + 1; + int bottom = y + rect.height + 1; + + Color oldColor = g.getColor(); + try + { + g.setColor(shadow); + if ((suppress & SUPPRESS_TOP) == 0) + g.drawLine(x, y, right, y); + if ((suppress & SUPPRESS_LEFT) == 0) + g.drawLine(x, y, x, bottom); + else + g.drawLine(x, bottom, x, bottom); // one pixel + + g.setColor(highlight); + if ((suppress & SUPPRESS_BOTTOM) == 0) + g.drawLine(x + (shadeBottomLeftPixel ? 1 : 0), bottom, right, bottom); + else if (!shadeBottomLeftPixel) + g.drawLine(x, bottom, x, bottom); // one pixel + + if ((suppress & SUPPRESS_RIGHT) == 0) + g.drawLine(right, y, right, bottom); + } + finally + { + g.setColor(oldColor); + } + } + + + /** + * Measures the width of this border. + * + * @param c the component whose border is to be measured, usually + * an instance of {@link javax.swing.JSplitPane}. + * + * @return an Insets object whose <code>left</code>, + * <code>right</code>, <code>top</code> and + * <code>bottom</code> fields indicate the width of the + * border at the respective edge. + */ + public Insets getBorderInsets(Component c) + { + return new Insets(1, 1, 1, 1); + } + + + /** + * Determines whether this border fills every pixel in its area + * when painting. + * + * @return <code>false</code> because this border does not + * paint over the pixels where the divider joins + * the border. + */ + public boolean isBorderOpaque() + { + /* Strangely, the Sun implementation (tested with JDK 1.3.1 and + * 1.4.1_01) seems to always return true. It could be a bug, + * but without knowing the details of their implementation, it is + * hard to decide. + */ + return false; + } + } + + + /** + * A border for the divider inside a JSplitPane. + * + * <p><img src="doc-files/BasicBorders.SplitPaneDividerBorder-1.png" + * width="520" height="200" alt="[A screen shot of this border]" /> + * + * @author Sascha Brawer (brawer@dandelis.ch) + */ + private static class SplitPaneDividerBorder + implements Border, UIResource, Serializable + { + /** + * Constructs a new border for drawing the divider of a JSplitPane + * in the Basic look and feel. The outer parts of the JSplitPane have + * their own border class, <code>SplitPaneBorder</code>. + */ + public SplitPaneDividerBorder() + { + // Nothing to do here. + } + + /** + * Paints the border around the divider of a <code>JSplitPane</code>. + * + * <p><img src="doc-files/BasicBorders.SplitPaneDividerBorder-1.png" + * width="520" height="200" alt="[A picture that shows which pixels + * get painted in what color]" /> + * + * @param c the <code>JSplitPane</code> whose divider’s border + * is to be painted. + * @param g the graphics for painting. + * @param x the horizontal position for painting the border. + * @param y the vertical position for painting the border. + * @param width the width of the available area for painting the border. + * @param height the height of the available area for painting the border. + */ + public void paintBorder(Component c, Graphics g, + int x, int y, int width, int height) + { + Color highlight = UIManager.getColor("SplitPane.highlight"); + Color shadow = UIManager.getColor("SplitPane.shadow"); + Color oldColor, dcol; + int x2, y2; + JSplitPane sp; + + sp = getSplitPane(c); + if (sp == null) + return; + + x2 = x + width - 1; + y2 = y + height - 1; + oldColor = g.getColor(); + dcol = c.getBackground(); + try + { + switch (sp.getOrientation()) + { + case JSplitPane.HORIZONTAL_SPLIT: + g.setColor(dcol); + g.drawLine(x + 1, y, x2 - 1, y); + g.drawLine(x + 1, y2, x2 - 1, y2); + g.setColor(sp.getLeftComponent() != null ? highlight : dcol); + g.drawLine(x, y, x, y2); + g.setColor(sp.getRightComponent() != null ? shadow : dcol); + g.drawLine(x2, y, x2, y2); + break; + + case JSplitPane.VERTICAL_SPLIT: + g.setColor(dcol); + g.drawLine(x, y + 1, x, y2 - 1); + g.drawLine(x2, y + 1, x2, y2 - 1); + g.setColor(sp.getTopComponent() != null ? highlight : dcol); + g.drawLine(x, y, x2, y); + g.setColor(sp.getBottomComponent() != null ? shadow : dcol); + g.drawLine(x, y2, x2, y2); + break; + } + } + finally + { + g.setColor(oldColor); + } + } + + + /** + * Measures the width of this border. + * + * @param c the component whose border is to be measured, usually + * an instance of {@link javax.swing.JSplitPane}. + * + * @return an Insets object whose <code>left</code>, + * <code>right</code>, <code>top</code> and + * <code>bottom</code> fields indicate the width of the + * border at the respective edge. + */ + public Insets getBorderInsets(Component c) + { + return new Insets(1, 1, 1, 1); + } + + /** + * Determines whether this border fills every pixel in its area + * when painting. + * + * @return <code>true</code> + */ + public boolean isBorderOpaque() + { + return true; + } + + + /** + * Determines the JSplitPane whose divider is being painted. + * + * @param c an instance of BasicSplitPaneDivider. + * + * @return a <code>JSplitPane</code>, or <code>null</code> if + * <code>c</code> is not an instance of {@link + * javax.swing.plaf.basic.BasicSplitPaneDivider}. + */ + private JSplitPane getSplitPane(Component c) + { + if (c instanceof BasicSplitPaneDivider) + return (((BasicSplitPaneDivider) c).getBasicSplitPaneUI()) + .getSplitPane(); + else + return null; + } + } + + + /** + * A border for toggle buttons in the Basic look and feel. + * + * <p><img src="doc-files/BasicBorders.ToggleButtonBorder-1.png" + * width="270" height="135" alt="[A screen shot of this border]" /> + * + * <p>The Sun implementation always seems to draw exactly + * the same border, irrespective of the state of the button. + * This is rather surprising, but GNU Classpath emulates the + * observable behavior. + * + * @see javax.swing.plaf.basic.BasicGraphicsUtils#drawBezel + * + * @author Sascha Brawer (brawer@dandelis.ch) + */ + public static class ToggleButtonBorder + extends ButtonBorder + { + /** + * Determined using the <code>serialver</code> tool + * of Apple/Sun JDK 1.3.1 on MacOS X 10.1.5. + */ + static final long serialVersionUID = -3528666548001058394L; + + + /** + * Constructs a new border for drawing a JToggleButton in + * the Basic look and feel. + * + * @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 ToggleButtonBorder(Color shadow, Color darkShadow, + Color highlight, Color lightHighlight) + { + /* The superclass ButtonBorder substitutes null arguments + * with fallback colors. + */ + super(shadow, darkShadow, highlight, lightHighlight); + } + + + /** + * Paints the ToggleButtonBorder around a given component. + * + * <p>The Sun implementation always seems to draw exactly + * the same border, irrespective of the state of the button. + * This is rather surprising, but GNU Classpath emulates the + * observable behavior. + * + * @param c the component whose border is to be painted. + * @param g the graphics for painting. + * @param x the horizontal position for painting the border. + * @param y the vertical position for painting the border. + * @param width the width of the available area for painting the border. + * @param height the height of the available area for painting the border. + * + * @see javax.swing.plaf.basic.BasicGraphicsUtils#drawBezel + */ + public void paintBorder(Component c, Graphics g, + int x, int y, int width, int height) + { + /* The author of this code tried various variants for setting + * the state of the enclosed JToggleButton, but it seems that + * the drawn border is always identical. Weird, because this + * means that the user does not see whether the JToggleButton + * is selected or not. + */ + BasicGraphicsUtils.drawBezel(g, x, y, width, height, + /* pressed */ false, + /* default */ false, + shadow, darkShadow, + highlight, lightHighlight); + } + + + /** + * Measures the width of this border. + * + * @param c the component whose border is to be measured. + * + * @return an Insets object whose <code>left</code>, + * <code>right</code>, <code>top</code> and + * <code>bottom</code> fields indicate the width of the + * border at the respective edge. + * + * @see #getBorderInsets(java.awt.Component, java.awt.Insets) + */ + public Insets getBorderInsets(Component c) + { + /* There is no obvious reason for overriding this method, but we + * try to have exactly the same API as the Sun reference + * implementation. + */ + return getBorderInsets(c, null); + } + + + /** + * Measures the width of this border, storing the results into a + * pre-existing Insets object. + * + * @param insets an Insets object for holding the result values. + * After invoking this method, the <code>left</code>, + * <code>right</code>, <code>top</code> and + * <code>bottom</code> fields indicate the width of the + * border at the respective edge. + * + * @return the same object that was passed for <code>insets</code>. + * + * @see #getBorderInsets(Component) + */ + public Insets getBorderInsets(Component c, Insets insets) + { + /* The exact amount has been determined using a test program + * that was run on the Apple/Sun JDK 1.3.1 on MacOS X, and the + * Sun JDK 1.4.1_01 on GNU/Linux for x86. Both gave [2,2,2,2]. + */ + if (insets == null) + return new Insets(2, 2, 2, 2); + + insets.left = insets.right = insets.top = insets.bottom = 2; + return insets; + } + } + +} diff --git a/libjava/classpath/javax/swing/plaf/basic/BasicButtonListener.java b/libjava/classpath/javax/swing/plaf/basic/BasicButtonListener.java new file mode 100644 index 000000000..22033b6ad --- /dev/null +++ b/libjava/classpath/javax/swing/plaf/basic/BasicButtonListener.java @@ -0,0 +1,370 @@ +/* BasicButtonListener.java -- + Copyright (C) 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.event.ActionEvent; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; +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.Action; +import javax.swing.ActionMap; +import javax.swing.ButtonModel; +import javax.swing.InputMap; +import javax.swing.JComponent; +import javax.swing.SwingUtilities; +import javax.swing.UIManager; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import javax.swing.plaf.ActionMapUIResource; +import javax.swing.plaf.ButtonUI; + +public class BasicButtonListener + implements MouseListener, MouseMotionListener, FocusListener, ChangeListener, + PropertyChangeListener +{ + /** + * Implements the keyboard action for Swing buttons. + */ + private class ButtonAction + extends AbstractAction + { + /** + * The key for pressed action. + */ + static final String PRESSED = "pressed"; + + /** + * The key for released action. + */ + static final String RELEASED = "released"; + + /** + * Performs the action. + */ + public void actionPerformed(ActionEvent event) + { + Object cmd = getValue("__command__"); + AbstractButton b = (AbstractButton) event.getSource(); + ButtonModel m = b.getModel(); + if (PRESSED.equals(cmd)) + { + m.setArmed(true); + m.setPressed(true); + if (! b.isFocusOwner()) + b.requestFocus(); + } + else if (RELEASED.equals(cmd)) + { + m.setPressed(false); + m.setArmed(false); + } + } + + /** + * Indicates if this action is enabled. + * + * @param source the source of the action + * + * @return <code>true</code> when enabled, <code>false</code> otherwise + */ + public boolean isEnabled(Object source) + { + boolean enabled = true; + if (source instanceof AbstractButton) + { + AbstractButton b = (AbstractButton) source; + enabled = b.isEnabled(); + } + return enabled; + } + } + + public BasicButtonListener(AbstractButton b) + { + // Do nothing here. + } + + public void propertyChange(PropertyChangeEvent e) + { + // Store the TextLayout for this in a client property for speed-up + // painting of the label. + String property = e.getPropertyName(); + AbstractButton b = (AbstractButton) e.getSource(); + if ((property.equals(AbstractButton.TEXT_CHANGED_PROPERTY) + || property.equals("font")) + && SystemProperties.getProperty("gnu.javax.swing.noGraphics2D") + == null) + { + 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); + + // Update HTML renderer. + BasicHTML.updateRenderer(b, b.getText()); + } + else if (property.equals(AbstractButton.CONTENT_AREA_FILLED_CHANGED_PROPERTY)) + { + checkOpacity(b); + } + } + + /** + * Checks the <code>contentAreaFilled</code> property and updates the + * opaque property of the button. + * + * @param b the button to check + */ + protected void checkOpacity(AbstractButton b) + { + b.setOpaque(b.isContentAreaFilled()); + } + + public void focusGained(FocusEvent e) + { + if (e.getSource() instanceof AbstractButton) + { + AbstractButton button = (AbstractButton) e.getSource(); + if (button.isFocusPainted()) + button.repaint(); + } + } + + public void focusLost(FocusEvent e) + { + if (e.getSource() instanceof AbstractButton) + { + AbstractButton button = (AbstractButton) e.getSource(); + if (button.isFocusPainted()) + button.repaint(); + } + } + + public void installKeyboardActions(JComponent c) + { + ButtonUI ui = ((AbstractButton) c).getUI(); + if (ui instanceof BasicButtonUI) + { + // Install InputMap. + BasicButtonUI basicUI = (BasicButtonUI) ui; + String prefix = basicUI.getPropertyPrefix(); + InputMap focusInputMap = + (InputMap) UIManager.get(prefix + "focusInputMap"); + SwingUtilities.replaceUIInputMap(c, JComponent.WHEN_FOCUSED, + focusInputMap); + + ActionMap am = (ActionMap) UIManager.get(prefix + "actionMap"); + if (am == null) + { + am = createDefaultActionMap(); + UIManager.put(prefix + "actionMap", am); + } + SwingUtilities.replaceUIActionMap(c, am); + } + + c.getActionMap().put("pressed", + new AbstractAction() + { + public void actionPerformed(ActionEvent e) + { + AbstractButton button = (AbstractButton) e.getSource(); + ButtonModel model = button.getModel(); + // It is important that these transitions happen in this order. + model.setArmed(true); + model.setPressed(true); + } + }); + + c.getActionMap().put("released", + new AbstractAction() + { + public void actionPerformed(ActionEvent e) + { + AbstractButton button = (AbstractButton) e.getSource(); + ButtonModel model = button.getModel(); + // It is important that these transitions happen in this order. + model.setPressed(false); + model.setArmed(false); + } + }); + } + + /** + * Creates and returns the default action map for Swing buttons. + * + * @return the default action map for Swing buttons + */ + private ActionMap createDefaultActionMap() + { + Action action = new ButtonAction(); + ActionMapUIResource am = new ActionMapUIResource(); + am.put(ButtonAction.PRESSED, action); + am.put(ButtonAction.RELEASED, action); + return am; + } + + public void uninstallKeyboardActions(JComponent c) + { + SwingUtilities.replaceUIActionMap(c, null); + SwingUtilities.replaceUIInputMap(c, JComponent.WHEN_FOCUSED, null); + } + + public void stateChanged(ChangeEvent e) + { + // Need to repaint when the button state changes. + ((AbstractButton) e.getSource()).repaint(); + } + + public void mouseMoved(MouseEvent e) + { + // Nothing to do here. + } + + public void mouseDragged(MouseEvent e) + { + // Nothing to do here. + } + + public void mouseClicked(MouseEvent e) + { + // Nothing to do here. + } + + /** + * Accept a mouse press event and arm the button. + * + * @param e The mouse press event to accept + */ + public void mousePressed(MouseEvent e) + { + if (e.getSource() instanceof AbstractButton) + { + AbstractButton button = (AbstractButton) e.getSource(); + ButtonModel model = button.getModel(); + if (SwingUtilities.isLeftMouseButton(e)) + { + // It is important that these transitions happen in this order. + model.setArmed(true); + model.setPressed(true); + + if (! button.isFocusOwner() && button.isRequestFocusEnabled()) + button.requestFocus(); + } + } + } + + /** + * Accept a mouse release event and set the button's + * "pressed" property to <code>true</code>, if the model + * is armed. If the model is not armed, ignore the event. + * + * @param e The mouse release event to accept + */ + public void mouseReleased(MouseEvent e) + { + if (e.getSource() instanceof AbstractButton) + { + AbstractButton button = (AbstractButton) e.getSource(); + ButtonModel model = button.getModel(); + if (e.getButton() == MouseEvent.BUTTON1) + { + // It is important that these transitions happen in this order. + model.setPressed(false); + model.setArmed(false); + } + } + } + + /** + * Accept a mouse enter event and set the button's "rollover" property to + * <code>true</code>, if the button's "rolloverEnabled" property is + * <code>true</code>. If the button is currently armed and the mouse + * button is not held down, this enter event will also disarm the model. + * + * @param e The mouse enter event to accept + */ + public void mouseEntered(MouseEvent e) + { + if (e.getSource() instanceof AbstractButton) + { + AbstractButton button = (AbstractButton) e.getSource(); + ButtonModel model = button.getModel(); + if (button.isRolloverEnabled() + && ! SwingUtilities.isLeftMouseButton(e)) + model.setRollover(true); + + if (model.isPressed()) + model.setArmed(true); + } + } + + /** + * Accept a mouse exit event and set the button's model's "rollover" + * property to <code>false</code>, if it's "rolloverEnabled" property is + * <code>true</code>. Also disarm the button. + * + * @param e The mouse exit event to accept + */ + public void mouseExited(MouseEvent e) + { + if (e.getSource() instanceof AbstractButton) + { + AbstractButton button = (AbstractButton) e.getSource(); + ButtonModel model = button.getModel(); + if (button.isRolloverEnabled()) + model.setRollover(false); + model.setArmed(false); + } + } +} diff --git a/libjava/classpath/javax/swing/plaf/basic/BasicButtonUI.java b/libjava/classpath/javax/swing/plaf/basic/BasicButtonUI.java new file mode 100644 index 000000000..1697c24d9 --- /dev/null +++ b/libjava/classpath/javax/swing/plaf/basic/BasicButtonUI.java @@ -0,0 +1,636 @@ +/* BasicButtonUI.java -- + Copyright (C) 2002, 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 java.awt.Dimension; +import java.awt.Font; +import java.awt.FontMetrics; +import java.awt.Graphics; +import java.awt.Insets; +import java.awt.Rectangle; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; + +import javax.swing.AbstractButton; +import javax.swing.ButtonModel; +import javax.swing.Icon; +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.LookAndFeel; +import javax.swing.SwingUtilities; +import javax.swing.UIManager; +import javax.swing.plaf.ButtonUI; +import javax.swing.plaf.ComponentUI; +import javax.swing.plaf.UIResource; +import javax.swing.text.View; + +/** + * A UI delegate for the {@link JButton} component. + */ +public class BasicButtonUI extends ButtonUI +{ + /** + * Cached rectangle for layouting the label. Used in paint() and + * BasicGraphicsUtils.getPreferredButtonSize(). + */ + static Rectangle viewR = new Rectangle(); + + /** + * Cached rectangle for layouting the label. Used in paint() and + * BasicGraphicsUtils.getPreferredButtonSize(). + */ + static Rectangle iconR = new Rectangle(); + + /** + * Cached rectangle for layouting the label. Used in paint() and + * BasicGraphicsUtils.getPreferredButtonSize(). + */ + static Rectangle textR = new Rectangle(); + + /** + * Cached Insets instance, used in paint(). + */ + static Insets cachedInsets; + + /** + * The shared button UI. + */ + private static BasicButtonUI sharedUI; + + /** + * The shared BasicButtonListener. + */ + private static BasicButtonListener sharedListener; + + /** + * A constant used to pad out elements in the button's layout and + * preferred size calculations. + */ + protected int defaultTextIconGap = 4; + + /** + * A constant added to the defaultTextIconGap to adjust the text + * within this particular button. + */ + protected int defaultTextShiftOffset; + + private int textShiftOffset; + + /** + * Factory method to create an instance of BasicButtonUI for a given + * {@link JComponent}, which should be an {@link AbstractButton}. + * + * @param c The component. + * + * @return A new UI capable of drawing the component + */ + public static ComponentUI createUI(final JComponent c) + { + if (sharedUI == null) + sharedUI = new BasicButtonUI(); + return sharedUI; + } + + /** + * Returns the default gap between the button's text and icon (in pixels). + * + * @param b the button (ignored). + * + * @return The gap. + */ + public int getDefaultTextIconGap(AbstractButton b) + { + return defaultTextIconGap; + } + + /** + * Sets the text shift offset to zero. + * + * @see #setTextShiftOffset() + */ + protected void clearTextShiftOffset() + { + textShiftOffset = 0; + } + + /** + * Returns the text shift offset. + * + * @return The text shift offset. + * + * @see #clearTextShiftOffset() + * @see #setTextShiftOffset() + */ + protected int getTextShiftOffset() + { + return textShiftOffset; + } + + /** + * Sets the text shift offset to the value in {@link #defaultTextShiftOffset}. + * + * @see #clearTextShiftOffset() + */ + protected void setTextShiftOffset() + { + textShiftOffset = defaultTextShiftOffset; + } + + /** + * Returns the prefix for the UI defaults property for this UI class. + * This is 'Button' for this class. + * + * @return the prefix for the UI defaults property + */ + protected String getPropertyPrefix() + { + return "Button."; + } + + /** + * Installs the default settings. + * + * @param b the button (<code>null</code> not permitted). + */ + protected void installDefaults(AbstractButton b) + { + String prefix = getPropertyPrefix(); + // Install colors and font. + LookAndFeel.installColorsAndFont(b, prefix + "background", + prefix + "foreground", prefix + "font"); + // Install border. + LookAndFeel.installBorder(b, prefix + "border"); + + // Install margin property. + if (b.getMargin() == null || b.getMargin() instanceof UIResource) + b.setMargin(UIManager.getInsets(prefix + "margin")); + + // Install rollover property. + Object rollover = UIManager.get(prefix + "rollover"); + if (rollover != null) + LookAndFeel.installProperty(b, "rolloverEnabled", rollover); + + // Fetch default textShiftOffset. + defaultTextShiftOffset = UIManager.getInt(prefix + "textShiftOffset"); + + // Make button opaque if needed. + if (b.isContentAreaFilled()) + LookAndFeel.installProperty(b, "opaque", Boolean.TRUE); + else + LookAndFeel.installProperty(b, "opaque", Boolean.FALSE); + } + + /** + * Removes the defaults added by {@link #installDefaults(AbstractButton)}. + * + * @param b the button (<code>null</code> not permitted). + */ + protected void uninstallDefaults(AbstractButton b) + { + // The other properties aren't uninstallable. + LookAndFeel.uninstallBorder(b); + } + + /** + * Creates and returns a new instance of {@link BasicButtonListener}. This + * method provides a hook to make it easy for subclasses to install a + * different listener. + * + * @param b the button. + * + * @return A new listener. + */ + protected BasicButtonListener createButtonListener(AbstractButton b) + { + // Note: The RI always returns a new instance here. However, + // the BasicButtonListener class is perfectly suitable to be shared + // between multiple buttons, so we return a shared instance here + // for efficiency. + if (sharedListener == null) + sharedListener = new BasicButtonListener(b); + return sharedListener; + } + + /** + * Installs listeners for the button. + * + * @param b the button (<code>null</code> not permitted). + */ + protected void installListeners(AbstractButton b) + { + BasicButtonListener listener = createButtonListener(b); + if (listener != null) + { + b.addChangeListener(listener); + b.addPropertyChangeListener(listener); + b.addFocusListener(listener); + b.addMouseListener(listener); + b.addMouseMotionListener(listener); + } + // Fire synthetic property change event to let the listener update + // the TextLayout cache. + listener.propertyChange(new PropertyChangeEvent(b, "font", null, + b.getFont())); + } + + /** + * Uninstalls listeners for the button. + * + * @param b the button (<code>null</code> not permitted). + */ + protected void uninstallListeners(AbstractButton b) + { + BasicButtonListener listener = getButtonListener(b); + if (listener != null) + { + b.removeChangeListener(listener); + b.removePropertyChangeListener(listener); + b.removeFocusListener(listener); + b.removeMouseListener(listener); + b.removeMouseMotionListener(listener); + } + } + + protected void installKeyboardActions(AbstractButton b) + { + BasicButtonListener listener = getButtonListener(b); + if (listener != null) + listener.installKeyboardActions(b); + } + + protected void uninstallKeyboardActions(AbstractButton b) + { + BasicButtonListener listener = getButtonListener(b); + if (listener != null) + listener.uninstallKeyboardActions(b); + } + + /** + * Install the BasicButtonUI as the UI for a particular component. + * This means registering all the UI's listeners with the component, + * and setting any properties of the button which are particular to + * this look and feel. + * + * @param c The component to install the UI into + */ + public void installUI(final JComponent c) + { + super.installUI(c); + if (c instanceof AbstractButton) + { + AbstractButton b = (AbstractButton) c; + installDefaults(b); + // It is important to install the listeners before installing + // the keyboard actions, because the keyboard actions + // are actually installed on the listener instance. + installListeners(b); + installKeyboardActions(b); + BasicHTML.updateRenderer(b, b.getText()); + } + } + + /** + * Uninstalls the UI from the component. + * + * @param c the component from which to uninstall the UI + */ + public void uninstallUI(JComponent c) + { + if (c instanceof AbstractButton) + { + AbstractButton b = (AbstractButton) c; + uninstallKeyboardActions(b); + uninstallListeners(b); + uninstallDefaults(b); + BasicHTML.updateRenderer(b, ""); + b.putClientProperty(BasicGraphicsUtils.CACHED_TEXT_LAYOUT, null); + } + } + + /** + * Calculates the minimum size for the specified component. + * + * @param c the component for which to compute the minimum size + * + * @return the minimum size for the specified component + */ + public Dimension getMinimumSize(JComponent c) + { + Dimension size = getPreferredSize(c); + // When the HTML view has a minimum width different from the preferred + // width, then substract this here accordingly. The height is not + // affected by that. + View html = (View) c.getClientProperty(BasicHTML.propertyKey); + if (html != null) + { + size.width -= html.getPreferredSpan(View.X_AXIS) + - html.getPreferredSpan(View.X_AXIS); + } + return size; + } + + /** + * Calculates the maximum size for the specified component. + * + * @param c the component for which to compute the maximum size + * + * @return the maximum size for the specified component + */ + public Dimension getMaximumSize(JComponent c) + { + Dimension size = getPreferredSize(c); + // When the HTML view has a maximum width different from the preferred + // width, then add this here accordingly. The height is not + // affected by that. + View html = (View) c.getClientProperty(BasicHTML.propertyKey); + if (html != null) + { + size.width += html.getMaximumSpan(View.X_AXIS) + - html.getPreferredSpan(View.X_AXIS); + } + return size; + } + + /** + * Calculate the preferred size of this component, by delegating to + * {@link BasicGraphicsUtils#getPreferredButtonSize}. + * + * @param c The component to measure + * + * @return The preferred dimensions of the component + */ + public Dimension getPreferredSize(JComponent c) + { + AbstractButton b = (AbstractButton) c; + Dimension d = BasicGraphicsUtils.getPreferredButtonSize(b, + b.getIconTextGap()); + return d; + } + + static Icon currentIcon(AbstractButton b) + { + Icon i = b.getIcon(); + ButtonModel model = b.getModel(); + + if (model.isPressed() && b.getPressedIcon() != null && b.isEnabled()) + i = b.getPressedIcon(); + + else if (model.isRollover()) + { + if (b.isSelected() && b.getRolloverSelectedIcon() != null) + i = b.getRolloverSelectedIcon(); + else if (b.getRolloverIcon() != null) + i = b.getRolloverIcon(); + } + + else if (b.isSelected() && b.isEnabled()) + { + if (b.isEnabled() && b.getSelectedIcon() != null) + i = b.getSelectedIcon(); + else if (b.getDisabledSelectedIcon() != null) + i = b.getDisabledSelectedIcon(); + } + + else if (! b.isEnabled() && b.getDisabledIcon() != null) + i = b.getDisabledIcon(); + + return i; + } + + /** + * Paint the component, which is an {@link AbstractButton}, according to + * its current state. + * + * @param g The graphics context to paint with + * @param c The component to paint the state of + */ + public void paint(Graphics g, JComponent c) + { + AbstractButton b = (AbstractButton) c; + + Insets i = c.getInsets(cachedInsets); + viewR.x = i.left; + viewR.y = i.top; + viewR.width = c.getWidth() - i.left - i.right; + viewR.height = c.getHeight() - i.top - i.bottom; + textR.x = 0; + textR.y = 0; + textR.width = 0; + textR.height = 0; + iconR.x = 0; + iconR.y = 0; + iconR.width = 0; + iconR.height = 0; + + Font f = c.getFont(); + g.setFont(f); + Icon icon = b.getIcon(); + String text = b.getText(); + text = SwingUtilities.layoutCompoundLabel(c, g.getFontMetrics(f), + text, icon, + b.getVerticalAlignment(), + b.getHorizontalAlignment(), + b.getVerticalTextPosition(), + b.getHorizontalTextPosition(), + viewR, iconR, textR, + text == null ? 0 + : b.getIconTextGap()); + + ButtonModel model = b.getModel(); + if (model.isArmed() && model.isPressed()) + paintButtonPressed(g, b); + + if (icon != null) + paintIcon(g, c, iconR); + if (text != null) + { + View html = (View) b.getClientProperty(BasicHTML.propertyKey); + if (html != null) + html.paint(g, textR); + else + paintText(g, b, textR, text); + } + if (b.isFocusOwner() && b.isFocusPainted()) + paintFocus(g, b, viewR, textR, iconR); + } + + /** + * Paint any focus decoration this {@link JComponent} might have. The + * component, which in this case will be an {@link AbstractButton}, + * should only have focus decoration painted if it has the focus, and its + * "focusPainted" property is <code>true</code>. + * + * @param g Graphics context to paint with + * @param b Button to paint the focus of + * @param vr Visible rectangle, the area in which to paint + * @param tr Text rectangle, contained in visible rectangle + * @param ir Icon rectangle, contained in visible rectangle + * + * @see AbstractButton#isFocusPainted() + * @see JComponent#hasFocus() + */ + protected void paintFocus(Graphics g, AbstractButton b, Rectangle vr, + Rectangle tr, Rectangle ir) + { + // In the BasicLookAndFeel no focus border is drawn. This can be + // overridden in subclasses to implement such behaviour. + } + + /** + * Paint the icon for this component. Depending on the state of the + * component and the availability of the button's various icon + * properties, this might mean painting one of several different icons. + * + * @param g Graphics context to paint with + * @param c Component to paint the icon of + * @param iconRect Rectangle in which the icon should be painted + */ + protected void paintIcon(Graphics g, JComponent c, Rectangle iconRect) + { + AbstractButton b = (AbstractButton) c; + Icon i = currentIcon(b); + + if (i != null) + { + ButtonModel m = b.getModel(); + if (m.isPressed() && m.isArmed()) + { + int offs = getTextShiftOffset(); + i.paintIcon(c, g, iconRect.x + offs, iconRect.y + offs); + } + else + i.paintIcon(c, g, iconRect.x, iconRect.y); + } + } + + /** + * Paints the background area of an {@link AbstractButton} in the pressed + * state. This means filling the supplied area with a darker than normal + * background. + * + * @param g The graphics context to paint with + * @param b The button to paint the state of + */ + protected void paintButtonPressed(Graphics g, AbstractButton b) + { + if (b.isContentAreaFilled() && b.isOpaque()) + { + Rectangle area = new Rectangle(); + SwingUtilities.calculateInnerArea(b, area); + g.setColor(UIManager.getColor(getPropertyPrefix() + "shadow")); + g.fillRect(area.x, area.y, area.width, area.height); + } + } + + /** + * Paints the "text" property of an {@link AbstractButton}. + * + * @param g The graphics context to paint with + * @param c The component to paint the state of + * @param textRect The area in which to paint the text + * @param text The text to paint + */ + protected void paintText(Graphics g, JComponent c, Rectangle textRect, + String text) + { + AbstractButton b = (AbstractButton) c; + Font f = b.getFont(); + g.setFont(f); + FontMetrics fm = g.getFontMetrics(f); + + if (b.isEnabled()) + { + g.setColor(b.getForeground()); + // 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")); + // FIXME: Underline mnemonic. + BasicGraphicsUtils.drawString(b, g, text, -1, textRect.x, + textRect.y + fm.getAscent()); + } + } + + /** + * Paints the "text" property of an {@link AbstractButton}. + * + * @param g The graphics context to paint with + * @param b The button to paint the state of + * @param textRect The area in which to paint the text + * @param text The text to paint + * + * @since 1.4 + */ + protected void paintText(Graphics g, AbstractButton b, Rectangle textRect, + String text) + { + paintText(g, (JComponent) b, textRect, text); + } + + /** + * A helper method that finds the BasicButtonListener for the specified + * button. This is there because this UI class is stateless and + * shared for all buttons, and thus can't store the listener + * as instance field. (We store our shared instance in sharedListener, + * however, subclasses may override createButtonListener() and we would + * be lost in this case). + * + * @param b the button + * + * @return the UI event listener + */ + private BasicButtonListener getButtonListener(AbstractButton b) + { + // The listener gets installed as PropertyChangeListener, + // so look for it in the list of property change listeners. + PropertyChangeListener[] listeners = b.getPropertyChangeListeners(); + BasicButtonListener l = null; + for (int i = 0; listeners != null && l == null && i < listeners.length; + i++) + { + if (listeners[i] instanceof BasicButtonListener) + l = (BasicButtonListener) listeners[i]; + } + return l; + } +} diff --git a/libjava/classpath/javax/swing/plaf/basic/BasicCheckBoxMenuItemUI.java b/libjava/classpath/javax/swing/plaf/basic/BasicCheckBoxMenuItemUI.java new file mode 100644 index 000000000..79ce7b834 --- /dev/null +++ b/libjava/classpath/javax/swing/plaf/basic/BasicCheckBoxMenuItemUI.java @@ -0,0 +1,102 @@ +/* BasicCheckBoxMenuItemUI.java -- + Copyright (C) 2002, 2004 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 java.awt.event.MouseEvent; + +import javax.swing.JComponent; +import javax.swing.JMenuItem; +import javax.swing.MenuElement; +import javax.swing.MenuSelectionManager; +import javax.swing.UIDefaults; +import javax.swing.plaf.ComponentUI; + +/** + * DOCUMENT ME! + */ +public class BasicCheckBoxMenuItemUI extends BasicMenuItemUI +{ + + /** + * Creates a new BasicCheckBoxMenuItemUI object. + */ + public BasicCheckBoxMenuItemUI() + { + super(); + } + + /** + * Factory method to create a BasicCheckBoxMenuItemUI for the given {@link + * JComponent}, which should be a JCheckBoxMenuItem + * + * @param c The {@link JComponent} a UI is being created for. + * + * @return A BasicCheckBoxMenuItemUI for the {@link JComponent}. + */ + public static ComponentUI createUI(final JComponent c) + { + return new BasicCheckBoxMenuItemUI(); + } + + /** + * Returns the prefix for entries in the {@link UIDefaults} table. + * + * @return "CheckBoxMenuItem" + */ + protected String getPropertyPrefix() + { + return "CheckBoxMenuItem"; + } + + /** + * DOCUMENT ME! + * + * @param item DOCUMENT ME! + * @param e DOCUMENT ME! + * @param path DOCUMENT ME! + * @param manager DOCUMENT ME! + */ + public void processMouseEvent(JMenuItem item, MouseEvent e, + MenuElement[] path, + MenuSelectionManager manager) + { + // TODO: May not be implemented properly. + item.processMouseEvent(e, path, manager); + } +} diff --git a/libjava/classpath/javax/swing/plaf/basic/BasicCheckBoxUI.java b/libjava/classpath/javax/swing/plaf/basic/BasicCheckBoxUI.java new file mode 100644 index 000000000..0a4da00b2 --- /dev/null +++ b/libjava/classpath/javax/swing/plaf/basic/BasicCheckBoxUI.java @@ -0,0 +1,75 @@ +/* BasicCheckBoxUI.java + Copyright (C) 2002, 2004, 2005, 2006, 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 javax.swing.JCheckBox; +import javax.swing.JComponent; +import javax.swing.UIManager; +import javax.swing.plaf.ComponentUI; + +/** + * A UI delegate for the {@link JCheckBox} component. + */ +public class BasicCheckBoxUI extends BasicRadioButtonUI +{ + + /** + * Returns a UI delegate (that is, an instance of this class) for the + * specified component. + * + * @param c the component (this should be a {@link JCheckBox}). + * + * @return A new instance of <code>BasicCheckBoxUI</code>. + */ + public static ComponentUI createUI(JComponent c) + { + return new BasicCheckBoxUI(); + } + + /** + * Returns the prefix for entries in the {@link UIManager} defaults table + * (<code>"CheckBox."</code> in this case). + * + * @return "CheckBox." + */ + public String getPropertyPrefix() + { + return "CheckBox."; + } +} diff --git a/libjava/classpath/javax/swing/plaf/basic/BasicColorChooserUI.java b/libjava/classpath/javax/swing/plaf/basic/BasicColorChooserUI.java new file mode 100644 index 000000000..10b56431a --- /dev/null +++ b/libjava/classpath/javax/swing/plaf/basic/BasicColorChooserUI.java @@ -0,0 +1,344 @@ +/* BasicColorChooserUI.java -- + Copyright (C) 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 java.awt.BorderLayout; +import java.awt.Container; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; + +import javax.swing.JColorChooser; +import javax.swing.JComponent; +import javax.swing.JPanel; +import javax.swing.JTabbedPane; +import javax.swing.LookAndFeel; +import javax.swing.colorchooser.AbstractColorChooserPanel; +import javax.swing.colorchooser.ColorChooserComponentFactory; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import javax.swing.plaf.ColorChooserUI; +import javax.swing.plaf.ComponentUI; + +/** + * This is the UI Class for the JColorChooser in the Basic Look and Feel. + */ +public class BasicColorChooserUI extends ColorChooserUI +{ + /** + * This helper class handles property changes from the JColorChooser. + */ + public class PropertyHandler implements PropertyChangeListener + { + /** + * This method is called when any of the properties of the JColorChooser + * change. + * + * @param e The PropertyChangeEvent. + */ + public void propertyChange(PropertyChangeEvent e) + { + if (e.getPropertyName() == JColorChooser.CHOOSER_PANELS_PROPERTY) + makeTabs(chooser.getChooserPanels()); + else if (e.getPropertyName() == JColorChooser.PREVIEW_PANEL_PROPERTY) + updatePreviewPanel(chooser.getPreviewPanel()); + else if (e.getPropertyName() == JColorChooser.SELECTION_MODEL_PROPERTY) + ((AbstractColorChooserPanel) pane.getSelectedComponent()) + .updateChooser(); + + chooser.repaint(); + } + } + + /** + * This is a helper class that listens to the Model of the JColorChooser for + * color change events so it can update the preview panel. + */ + private class PreviewListener implements ChangeListener + { + /** + * This method is called whenever the JColorChooser's color changes. + * + * @param e The ChangeEvent. + */ + public void stateChanged(ChangeEvent e) + { + if (pane != null) + { + AbstractColorChooserPanel panel = (AbstractColorChooserPanel) pane + .getSelectedComponent(); + if (panel != null) + panel.updateChooser(); + } + chooser.repaint(); + } + } + + /** + * This helper class listens to the JTabbedPane that is used for tab + * changes. + */ + private class TabPaneListener implements ChangeListener + { + /** + * This method is called whenever a different tab is selected in the + * JTabbedPane. + * + * @param e The ChangeEvent. + */ + public void stateChanged(ChangeEvent e) + { + // Need to do this because we don't update all the tabs when they're not + // visible, so they are not informed of new colors when they're hidden. + AbstractColorChooserPanel comp = (AbstractColorChooserPanel) pane + .getSelectedComponent(); + comp.updateChooser(); + } + } + + /** An array of default choosers to use in the JColorChooser. */ + protected AbstractColorChooserPanel[] defaultChoosers; + + /** The listener for the preview panel. */ + protected ChangeListener previewListener; + + /** The PropertyChangeListener for the JColorChooser. */ + protected PropertyChangeListener propertyChangeListener; + + /** + * The JColorChooser this is installed on. + */ + protected JColorChooser chooser; + + /** The JTabbedPane that is used. */ + JTabbedPane pane; + + /** The Container that holds the preview panel. */ + private Container prevContainer; + + /** + * Creates a new BasicColorChooserUI object. + */ + public BasicColorChooserUI() + { + super(); + } + + /** + * This method creates a new UI Component for the given JComponent. + * + * @param c The JComponent to create an UI for. + * + * @return A new BasicColorChooserUI. + */ + public static ComponentUI createUI(JComponent c) + { + return new BasicColorChooserUI(); + } + + /** + * This method creates the default chooser panels for the JColorChooser. + * + * @return The default chooser panels. + */ + protected AbstractColorChooserPanel[] createDefaultChoosers() + { + return ColorChooserComponentFactory.getDefaultChooserPanels(); + } + + /** + * This method installs the UI Component for the given JComponent. + * + * @param c The JComponent to install this UI for. + */ + public void installUI(JComponent c) + { + if (c instanceof JColorChooser) + { + chooser = (JColorChooser) c; + chooser.setLayout(new BorderLayout()); + + // Do this first, so we avoid doing work for property change events. + defaultChoosers = createDefaultChoosers(); + chooser.setChooserPanels(defaultChoosers); + pane = new JTabbedPane(); + + pane.addChangeListener(new ChangeListener() + { + public void stateChanged(ChangeEvent e) + { + pane.repaint(); + } + }); + + makeTabs(defaultChoosers); + + chooser.add(pane, BorderLayout.NORTH); + + installPreviewPanel(); + + installDefaults(); + installListeners(); + } + } + + /** + * This method adds tabs to the JTabbedPane for the chooserPanels defined in + * the JColorChooser. + * This is package-private to avoid an accessor method. + * + * @param panels The Panels that need tabs to be made for them. + */ + void makeTabs(AbstractColorChooserPanel[] panels) + { + pane.removeAll(); + for (int i = 0; i < panels.length; i++) + pane.addTab(panels[i].getDisplayName(), panels[i].getSmallDisplayIcon(), + panels[i]); + } + + /** + * This method uninstalls this UI for the given JComponent. + * + * @param c The JComponent that will have this UI removed. + */ + public void uninstallUI(JComponent c) + { + uninstallListeners(); + uninstallDefaults(); + uninstallDefaultChoosers(); + + pane = null; + chooser = null; + } + + /** + * Uninstalls the default color choosers that have been installed by this UI. + */ + protected void uninstallDefaultChoosers() + { + defaultChoosers = null; + } + + /** + * This method installs the preview panel for the JColorChooser. + */ + protected void installPreviewPanel() + { + updatePreviewPanel(ColorChooserComponentFactory.getPreviewPanel()); + } + + /** + * This is a helper method that swaps the existing preview panel with the + * given panel. + * This is package-private to avoid an accessor method. + * + * @param preview The new preview panel. + */ + void updatePreviewPanel(JComponent preview) + { + if (prevContainer == null) + { + prevContainer = new JPanel(); + prevContainer.setLayout(new BorderLayout()); + chooser.add(prevContainer, BorderLayout.CENTER); + } + prevContainer.removeAll(); + prevContainer.add(preview, BorderLayout.CENTER); + } + + /** + * This method installs the default properties given by the Basic Look and + * Feel. + */ + protected void installDefaults() + { + LookAndFeel.installColorsAndFont(chooser, "ColorChooser.background", + "ColorChooser.foreground", + "ColorChooser.font"); + } + + /** + * This method uninstalls the default properties given by the Basic Look and + * Feel. + */ + protected void uninstallDefaults() + { + chooser.setBackground(null); + chooser.setForeground(null); + chooser.setFont(null); + } + + /** + * This method installs any listeners required for this UI to function. + */ + protected void installListeners() + { + propertyChangeListener = createPropertyChangeListener(); + previewListener = new PreviewListener(); + + chooser.addPropertyChangeListener(propertyChangeListener); + chooser.getSelectionModel().addChangeListener(previewListener); + + pane.addChangeListener(new TabPaneListener()); + } + + /** + * This method creates the PropertyChangeListener used for listening to the + * JColorChooser. + * + * @return A PropertyChangeListener. + */ + protected PropertyChangeListener createPropertyChangeListener() + { + return new PropertyHandler(); + } + + /** + * This method uninstalls any listeners that were previously installed by + * the UI. + */ + protected void uninstallListeners() + { + chooser.removePropertyChangeListener(propertyChangeListener); + chooser.getSelectionModel().removeChangeListener(previewListener); + + previewListener = null; + propertyChangeListener = null; + } +} diff --git a/libjava/classpath/javax/swing/plaf/basic/BasicComboBoxEditor.java b/libjava/classpath/javax/swing/plaf/basic/BasicComboBoxEditor.java new file mode 100644 index 000000000..b04f3fa01 --- /dev/null +++ b/libjava/classpath/javax/swing/plaf/basic/BasicComboBoxEditor.java @@ -0,0 +1,181 @@ +/* BasicComboBoxEditor.java -- + Copyright (C) 2004, 2005, 2006 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 java.awt.Component; +import java.awt.event.ActionListener; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; + +import javax.swing.ComboBoxEditor; +import javax.swing.JTextField; + +/** + * An editor used by the {@link BasicComboBoxUI} class. This editor uses a + * {@link JTextField} as the editor component. + * + * @author Olga Rodimina + */ +public class BasicComboBoxEditor extends Object implements ComboBoxEditor, + FocusListener +{ + /** The editor component. */ + protected JTextField editor; + + /** + * Creates a new <code>BasicComboBoxEditor</code> instance. + */ + public BasicComboBoxEditor() + { + editor = new JTextField(); + editor.setBorder(null); + editor.setColumns(9); + } + + /** + * Returns the component that will be used by the combo box to display and + * edit the currently selected item in the combo box. + * + * @return The editor component, which is a {@link JTextField} in this case. + */ + public Component getEditorComponent() + { + return editor; + } + + /** + * Sets item that should be edited when any editing operation is performed + * by the user. The value is always equal to the currently selected value + * in the combo box. Thus whenever a different value is selected from the + * combo box list then this method should be called to change editing + * item to the new selected item. + * + * @param item item that is currently selected in the combo box + */ + public void setItem(Object item) + { + if (item == null) + editor.setText(""); + else + editor.setText(item.toString()); + } + + /** + * Returns the text from the editor component. + * + * @return The text from the editor component. + */ + public Object getItem() + { + return editor.getText(); + } + + /** + * Selects all the text in the editor component. + */ + public void selectAll() + { + editor.selectAll(); + } + + /** + * This method is called when textfield gains focus. This will enable + * editing of the selected item. + * + * @param e the FocusEvent describing change in focus. + */ + public void focusGained(FocusEvent e) + { + // FIXME: Need to implement + } + + /** + * This method is called when textfield loses focus. If during this time any + * editting operation was performed by the user, then it will be cancelled + * and selected item will not be changed. + * + * @param e the FocusEvent describing change in focus + */ + public void focusLost(FocusEvent e) + { + // FIXME: Need to implement + } + + /** + * Adds an {@link ActionListener} to the editor component. If the user will + * edit currently selected item in the textfield and pressEnter, then action + * will be performed. The actionPerformed of this ActionListener should + * change the selected item of the comboBox to the newly editted selected + * item. + * + * @param l the ActionListener responsible for changing selected item of the + * combo box when it is editted by the user. + */ + public void addActionListener(ActionListener l) + { + editor.addActionListener(l); + } + + /** + * Removes the {@link ActionListener} from the editor component. + * + * @param l the listener to remove. + */ + public void removeActionListener(ActionListener l) + { + editor.removeActionListener(l); + } + + /** + * A subclass of {@link BasicComboBoxEditor} that implements the + * {@link UIResource} interface. + */ + public static class UIResource extends BasicComboBoxEditor + implements javax.swing.plaf.UIResource + { + /** + * Creates a new <code>BasicComboBoxEditor.UIResource</code> instance. + */ + public UIResource() + { + // Nothing to do here. + } + } + +} diff --git a/libjava/classpath/javax/swing/plaf/basic/BasicComboBoxRenderer.java b/libjava/classpath/javax/swing/plaf/basic/BasicComboBoxRenderer.java new file mode 100644 index 000000000..761d7d11f --- /dev/null +++ b/libjava/classpath/javax/swing/plaf/basic/BasicComboBoxRenderer.java @@ -0,0 +1,151 @@ +/* BasicComboBoxRenderer.java -- + Copyright (C) 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 java.awt.Component; +import java.awt.Dimension; +import java.io.Serializable; + +import javax.swing.Icon; +import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.ListCellRenderer; +import javax.swing.border.Border; +import javax.swing.border.EmptyBorder; + +/** + * A renderer for a {@link JComboBox}. + * + * @author Olga Rodimina + */ +public class BasicComboBoxRenderer + extends JLabel + implements ListCellRenderer, Serializable +{ + /** + * A shared border instance for all renderers. + */ + protected static Border noFocusBorder = new EmptyBorder(1, 1, 1, 1); + + /** + * Creates a new <code>BasicComboBoxRenderer</code> object. + */ + public BasicComboBoxRenderer() + { + setOpaque(true); + setBorder(noFocusBorder); + } + + /** + * Returns preferredSize of the renderer + * + * @return preferredSize of the renderer + */ + public Dimension getPreferredSize() + { + if (this.getText() != null && ! this.getText().equals("")) + return super.getPreferredSize(); + else + { + // If the combo box option's text is empty or null, it won't size + // properly (ie, it'll be way too short)... so we throw in a dummy + // space to trick the superclass's sizing methods. + String oldText = this.getText(); + this.setText(" "); + Dimension d = super.getPreferredSize(); + this.setText(oldText); + return d; + } + } + + /** + * Returns a component that has been configured to display the given + * <code>value</code>. + * + * @param list List of items for which to the background and foreground + * colors + * @param value object that should be rendered in the cell + * @param index index of the cell in the list of items. + * @param isSelected draw cell highlighted if isSelected is true + * @param cellHasFocus draw focus rectangle around cell if the cell has + * focus + * + * @return Component that will be used to draw the desired cell. + */ + public Component getListCellRendererComponent(JList list, Object value, + int index, boolean isSelected, + boolean cellHasFocus) + { + if (isSelected) + { + setBackground(list.getSelectionBackground()); + setForeground(list.getSelectionForeground()); + } + else + { + setBackground(list.getBackground()); + setForeground(list.getForeground()); + } + setFont(list.getFont()); + + if (value instanceof Icon) + setIcon((Icon) value); + else + setText(value == null ? "" : value.toString()); + + return this; + } + + /** + * A subclass of {@link BasicComboBoxRenderer} that implements the + * {@link javax.swing.plaf.UIResource} interface. + */ + public static class UIResource extends BasicComboBoxRenderer + implements javax.swing.plaf.UIResource + { + /** + * Creates a new <code>UIResource</code> object. + */ + public UIResource() + { + // Nothing to do here. + } + } +} diff --git a/libjava/classpath/javax/swing/plaf/basic/BasicComboBoxUI.java b/libjava/classpath/javax/swing/plaf/basic/BasicComboBoxUI.java new file mode 100644 index 000000000..07d4f42cd --- /dev/null +++ b/libjava/classpath/javax/swing/plaf/basic/BasicComboBoxUI.java @@ -0,0 +1,1410 @@ +/* BasicComboBoxUI.java -- + Copyright (C) 2004, 2005, 2006, 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 java.awt.Color; +import java.awt.Component; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.Graphics; +import java.awt.Insets; +import java.awt.LayoutManager; +import java.awt.Rectangle; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; +import java.awt.event.MouseListener; +import java.awt.event.MouseMotionListener; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; + +import javax.accessibility.Accessible; +import javax.accessibility.AccessibleContext; +import javax.swing.CellRendererPane; +import javax.swing.ComboBoxEditor; +import javax.swing.ComboBoxModel; +import javax.swing.DefaultListCellRenderer; +import javax.swing.InputMap; +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JComponent; +import javax.swing.JList; +import javax.swing.ListCellRenderer; +import javax.swing.LookAndFeel; +import javax.swing.SwingUtilities; +import javax.swing.UIManager; +import javax.swing.event.ListDataEvent; +import javax.swing.event.ListDataListener; +import javax.swing.plaf.ComboBoxUI; +import javax.swing.plaf.ComponentUI; +import javax.swing.plaf.UIResource; + +/** + * A UI delegate for the {@link JComboBox} component. + * + * @author Olga Rodimina + * @author Robert Schuster + */ +public class BasicComboBoxUI extends ComboBoxUI +{ + /** + * The arrow button that is displayed in the right side of JComboBox. This + * button is used to hide and show combo box's list of items. + */ + protected JButton arrowButton; + + /** + * The combo box represented by this UI delegate. + */ + protected JComboBox comboBox; + + /** + * The component that is responsible for displaying/editing the selected + * item of the combo box. + * + * @see BasicComboBoxEditor#getEditorComponent() + */ + protected Component editor; + + /** + * A listener listening to focus events occurring in the {@link JComboBox}. + */ + protected FocusListener focusListener; + + /** + * A flag indicating whether JComboBox currently has the focus. + */ + protected boolean hasFocus; + + /** + * A listener listening to item events fired by the {@link JComboBox}. + */ + protected ItemListener itemListener; + + /** + * A listener listening to key events that occur while {@link JComboBox} has + * the focus. + */ + protected KeyListener keyListener; + + /** + * List used when rendering selected item of the combo box. The selection + * and foreground colors for combo box renderer are configured from this + * list. + */ + protected JList listBox; + + /** + * ListDataListener listening to JComboBox model + */ + protected ListDataListener listDataListener; + + /** + * Popup list containing the combo box's menu items. + */ + protected ComboPopup popup; + + protected KeyListener popupKeyListener; + + protected MouseListener popupMouseListener; + + protected MouseMotionListener popupMouseMotionListener; + + /** + * Listener listening to changes in the bound properties of JComboBox + */ + protected PropertyChangeListener propertyChangeListener; + + /* Size of the largest item in the comboBox + * This is package-private to avoid an accessor method. + */ + Dimension displaySize = new Dimension(); + + /** + * Used to render the combo box values. + */ + protected CellRendererPane currentValuePane; + + /** + * The current minimum size if isMinimumSizeDirty is false. + * Setup by getMinimumSize() and invalidated by the various listeners. + */ + protected Dimension cachedMinimumSize; + + /** + * Indicates whether or not the cachedMinimumSize field is valid or not. + */ + protected boolean isMinimumSizeDirty = true; + + /** + * Creates a new <code>BasicComboBoxUI</code> object. + */ + public BasicComboBoxUI() + { + currentValuePane = new CellRendererPane(); + cachedMinimumSize = new Dimension(); + } + + /** + * A factory method to create a UI delegate for the given + * {@link JComponent}, which should be a {@link JComboBox}. + * + * @param c The {@link JComponent} a UI is being created for. + * + * @return A UI delegate for the {@link JComponent}. + */ + public static ComponentUI createUI(JComponent c) + { + return new BasicComboBoxUI(); + } + + /** + * Installs the UI for the given {@link JComponent}. + * + * @param c the JComponent to install a UI for. + * + * @see #uninstallUI(JComponent) + */ + public void installUI(JComponent c) + { + super.installUI(c); + + if (c instanceof JComboBox) + { + isMinimumSizeDirty = true; + comboBox = (JComboBox) c; + installDefaults(); + popup = createPopup(); + listBox = popup.getList(); + + // Set editor and renderer for the combo box. Editor is used + // only if combo box becomes editable, otherwise renderer is used + // to paint the selected item; combobox is not editable by default. + ListCellRenderer renderer = comboBox.getRenderer(); + if (renderer == null || renderer instanceof UIResource) + comboBox.setRenderer(createRenderer()); + + ComboBoxEditor currentEditor = comboBox.getEditor(); + if (currentEditor == null || currentEditor instanceof UIResource) + { + currentEditor = createEditor(); + comboBox.setEditor(currentEditor); + } + + installComponents(); + installListeners(); + comboBox.setLayout(createLayoutManager()); + comboBox.setFocusable(true); + installKeyboardActions(); + comboBox.putClientProperty(BasicLookAndFeel.DONT_CANCEL_POPUP, + Boolean.TRUE); + } + } + + /** + * Uninstalls the UI for the given {@link JComponent}. + * + * @param c The JComponent that is having this UI removed. + * + * @see #installUI(JComponent) + */ + public void uninstallUI(JComponent c) + { + setPopupVisible(comboBox, false); + popup.uninstallingUI(); + uninstallKeyboardActions(); + comboBox.setLayout(null); + uninstallComponents(); + uninstallListeners(); + uninstallDefaults(); + comboBox = null; + } + + /** + * Installs the defaults that are defined in the {@link BasicLookAndFeel} + * for this {@link JComboBox}. + * + * @see #uninstallDefaults() + */ + protected void installDefaults() + { + LookAndFeel.installColorsAndFont(comboBox, "ComboBox.background", + "ComboBox.foreground", "ComboBox.font"); + LookAndFeel.installBorder(comboBox, "ComboBox.border"); + } + + /** + * Creates and installs the listeners for this UI. + * + * @see #uninstallListeners() + */ + protected void installListeners() + { + // install combo box's listeners + propertyChangeListener = createPropertyChangeListener(); + comboBox.addPropertyChangeListener(propertyChangeListener); + + focusListener = createFocusListener(); + comboBox.addFocusListener(focusListener); + + itemListener = createItemListener(); + comboBox.addItemListener(itemListener); + + keyListener = createKeyListener(); + comboBox.addKeyListener(keyListener); + + // install listeners that listen to combo box model + listDataListener = createListDataListener(); + comboBox.getModel().addListDataListener(listDataListener); + + // Install mouse and key listeners from the popup. + popupMouseListener = popup.getMouseListener(); + comboBox.addMouseListener(popupMouseListener); + + popupMouseMotionListener = popup.getMouseMotionListener(); + comboBox.addMouseMotionListener(popupMouseMotionListener); + + popupKeyListener = popup.getKeyListener(); + comboBox.addKeyListener(popupKeyListener); + } + + /** + * Uninstalls the defaults and sets any objects created during + * install to <code>null</code>. + * + * @see #installDefaults() + */ + protected void uninstallDefaults() + { + if (comboBox.getFont() instanceof UIResource) + comboBox.setFont(null); + + if (comboBox.getForeground() instanceof UIResource) + comboBox.setForeground(null); + + if (comboBox.getBackground() instanceof UIResource) + comboBox.setBackground(null); + + LookAndFeel.uninstallBorder(comboBox); + } + + /** + * Detaches all the listeners we attached in {@link #installListeners}. + * + * @see #installListeners() + */ + protected void uninstallListeners() + { + comboBox.removePropertyChangeListener(propertyChangeListener); + propertyChangeListener = null; + + comboBox.removeFocusListener(focusListener); + listBox.removeFocusListener(focusListener); + focusListener = null; + + comboBox.removeItemListener(itemListener); + itemListener = null; + + comboBox.removeKeyListener(keyListener); + keyListener = null; + + comboBox.getModel().removeListDataListener(listDataListener); + listDataListener = null; + + if (popupMouseListener != null) + comboBox.removeMouseListener(popupMouseListener); + popupMouseListener = null; + + if (popupMouseMotionListener != null) + comboBox.removeMouseMotionListener(popupMouseMotionListener); + popupMouseMotionListener = null; + + if (popupKeyListener != null) + comboBox.removeKeyListener(popupKeyListener); + popupKeyListener = null; + } + + /** + * Creates the popup that will contain list of combo box's items. + * + * @return popup containing list of combo box's items + */ + protected ComboPopup createPopup() + { + return new BasicComboPopup(comboBox); + } + + /** + * Creates a {@link KeyListener} to listen to key events. + * + * @return KeyListener that listens to key events. + */ + protected KeyListener createKeyListener() + { + return new KeyHandler(); + } + + /** + * Creates the {@link FocusListener} that will listen to changes in this + * JComboBox's focus. + * + * @return the FocusListener. + */ + protected FocusListener createFocusListener() + { + return new FocusHandler(); + } + + /** + * Creates a {@link ListDataListener} to listen to the combo box's data model. + * + * @return The new listener. + */ + protected ListDataListener createListDataListener() + { + return new ListDataHandler(); + } + + /** + * Creates an {@link ItemListener} that will listen to the changes in + * the JComboBox's selection. + * + * @return The ItemListener + */ + protected ItemListener createItemListener() + { + return new ItemHandler(); + } + + /** + * Creates a {@link PropertyChangeListener} to listen to the changes in + * the JComboBox's bound properties. + * + * @return The PropertyChangeListener + */ + protected PropertyChangeListener createPropertyChangeListener() + { + return new PropertyChangeHandler(); + } + + /** + * Creates and returns a layout manager for the combo box. Subclasses can + * override this method to provide a different layout. + * + * @return a layout manager for the combo box. + */ + protected LayoutManager createLayoutManager() + { + return new ComboBoxLayoutManager(); + } + + /** + * Creates a component that will be responsible for rendering the + * selected component in the combo box. + * + * @return A renderer for the combo box. + */ + protected ListCellRenderer createRenderer() + { + return new BasicComboBoxRenderer.UIResource(); + } + + /** + * Creates the component that will be responsible for displaying/editing + * the selected item in the combo box. This editor is used only when combo + * box is editable. + * + * @return A new component that will be responsible for displaying/editing + * the selected item in the combo box. + */ + protected ComboBoxEditor createEditor() + { + return new BasicComboBoxEditor.UIResource(); + } + + /** + * Installs the components for this JComboBox. ArrowButton, main + * part of combo box (upper part) and popup list of items are created and + * configured here. + */ + protected void installComponents() + { + // create and install arrow button + arrowButton = createArrowButton(); + comboBox.add(arrowButton); + if (arrowButton != null) + configureArrowButton(); + + if (comboBox.isEditable()) + addEditor(); + + comboBox.add(currentValuePane); + } + + /** + * Uninstalls components from this {@link JComboBox}. + * + * @see #installComponents() + */ + protected void uninstallComponents() + { + // Unconfigure arrow button. + if (arrowButton != null) + { + unconfigureArrowButton(); + } + + // Unconfigure editor. + if (editor != null) + { + unconfigureEditor(); + } + + comboBox.removeAll(); + arrowButton = null; + } + + /** + * Adds the current editor to the combo box. + */ + public void addEditor() + { + removeEditor(); + editor = comboBox.getEditor().getEditorComponent(); + if (editor != null) + { + configureEditor(); + comboBox.add(editor); + } + } + + /** + * Removes the current editor from the combo box. + */ + public void removeEditor() + { + if (editor != null) + { + unconfigureEditor(); + comboBox.remove(editor); + } + } + + /** + * Configures the editor for this combo box. + */ + protected void configureEditor() + { + editor.setFont(comboBox.getFont()); + if (popupKeyListener != null) + editor.addKeyListener(popupKeyListener); + if (keyListener != null) + editor.addKeyListener(keyListener); + comboBox.configureEditor(comboBox.getEditor(), + comboBox.getSelectedItem()); + } + + /** + * Unconfigures the editor for this combo box. + */ + protected void unconfigureEditor() + { + if (popupKeyListener != null) + editor.removeKeyListener(popupKeyListener); + if (keyListener != null) + editor.removeKeyListener(keyListener); + } + + /** + * Configures the arrow button. + * + * @see #configureArrowButton() + */ + public void configureArrowButton() + { + if (arrowButton != null) + { + arrowButton.setEnabled(comboBox.isEnabled()); + arrowButton.setFocusable(false); + arrowButton.addMouseListener(popup.getMouseListener()); + arrowButton.addMouseMotionListener(popup.getMouseMotionListener()); + + // Mark the button as not closing the popup, we handle this ourselves. + arrowButton.putClientProperty(BasicLookAndFeel.DONT_CANCEL_POPUP, + Boolean.TRUE); + } + } + + /** + * Unconfigures the arrow button. + * + * @see #configureArrowButton() + * + * @specnote The specification says this method is implementation specific + * and should not be used or overridden. + */ + public void unconfigureArrowButton() + { + if (arrowButton != null) + { + if (popupMouseListener != null) + arrowButton.removeMouseListener(popupMouseListener); + if (popupMouseMotionListener != null) + arrowButton.removeMouseMotionListener(popupMouseMotionListener); + } + } + + /** + * Creates an arrow button for this {@link JComboBox}. The arrow button is + * displayed at the right end of the combo box and is used to display/hide + * the drop down list of items. + * + * @return A new button. + */ + protected JButton createArrowButton() + { + return new BasicArrowButton(BasicArrowButton.SOUTH); + } + + /** + * Returns <code>true</code> if the popup is visible, and <code>false</code> + * otherwise. + * + * @param c The JComboBox to check + * + * @return <code>true</code> if popup part of the JComboBox is visible and + * <code>false</code> otherwise. + */ + public boolean isPopupVisible(JComboBox c) + { + return popup.isVisible(); + } + + /** + * Displays/hides the {@link JComboBox}'s list of items on the screen. + * + * @param c The combo box, for which list of items should be + * displayed/hidden + * @param v true if show popup part of the jcomboBox and false to hide. + */ + public void setPopupVisible(JComboBox c, boolean v) + { + if (v) + popup.show(); + else + popup.hide(); + } + + /** + * JComboBox is focus traversable if it is editable and not otherwise. + * + * @param c combo box for which to check whether it is focus traversable + * + * @return true if focus tranversable and false otherwise + */ + public boolean isFocusTraversable(JComboBox c) + { + if (!comboBox.isEditable()) + return true; + + return false; + } + + /** + * Paints given menu item using specified graphics context + * + * @param g The graphics context used to paint this combo box + * @param c comboBox which needs to be painted. + */ + public void paint(Graphics g, JComponent c) + { + hasFocus = comboBox.hasFocus(); + if (! comboBox.isEditable()) + { + Rectangle rect = rectangleForCurrentValue(); + paintCurrentValueBackground(g, rect, hasFocus); + paintCurrentValue(g, rect, hasFocus); + } + } + + /** + * Returns preferred size for the combo box. + * + * @param c comboBox for which to get preferred size + * + * @return The preferred size for the given combo box + */ + public Dimension getPreferredSize(JComponent c) + { + return getMinimumSize(c); + } + + /** + * Returns the minimum size for this {@link JComboBox} for this + * look and feel. Also makes sure cachedMinimimSize is setup correctly. + * + * @param c The {@link JComponent} to find the minimum size for. + * + * @return The dimensions of the minimum size. + */ + public Dimension getMinimumSize(JComponent c) + { + if (isMinimumSizeDirty) + { + Insets i = getInsets(); + Dimension d = getDisplaySize(); + d.width += i.left + i.right + d.height; + cachedMinimumSize = new Dimension(d.width, d.height + i.top + i.bottom); + isMinimumSizeDirty = false; + } + return new Dimension(cachedMinimumSize); + } + + /** + * Returns the maximum size for this {@link JComboBox} for this + * look and feel. + * + * @param c The {@link JComponent} to find the maximum size for + * + * @return The maximum size (<code>Dimension(32767, 32767)</code>). + */ + public Dimension getMaximumSize(JComponent c) + { + return new Dimension(32767, 32767); + } + + /** + * Returns the number of accessible children of the combobox. + * + * @param c the component (combobox) to check, ignored + * + * @return the number of accessible children of the combobox + */ + public int getAccessibleChildrenCount(JComponent c) + { + int count = 1; + if (comboBox.isEditable()) + count = 2; + return count; + } + + /** + * Returns the accessible child with the specified index. + * + * @param c the component, this is ignored + * @param i the index of the accessible child to return + */ + public Accessible getAccessibleChild(JComponent c, int i) + { + Accessible child = null; + switch (i) + { + case 0: // The popup. + if (popup instanceof Accessible) + { + AccessibleContext ctx = ((Accessible) popup).getAccessibleContext(); + ctx.setAccessibleParent(comboBox); + child = (Accessible) popup; + } + break; + case 1: // The editor, if any. + if (comboBox.isEditable() && editor instanceof Accessible) + { + AccessibleContext ctx = + ((Accessible) editor).getAccessibleContext(); + ctx.setAccessibleParent(comboBox); + child = (Accessible) editor; + } + break; + } + return child; + } + + /** + * Returns true if the specified key is a navigation key and false otherwise + * + * @param keyCode a key for which to check whether it is navigation key or + * not. + * + * @return true if the specified key is a navigation key and false otherwis + */ + protected boolean isNavigationKey(int keyCode) + { + return keyCode == KeyEvent.VK_UP || keyCode == KeyEvent.VK_DOWN + || keyCode == KeyEvent.VK_LEFT || keyCode == KeyEvent.VK_RIGHT + || keyCode == KeyEvent.VK_ENTER || keyCode == KeyEvent.VK_ESCAPE + || keyCode == KeyEvent.VK_TAB; + } + + /** + * Selects next possible item relative to the current selection + * to be next selected item in the combo box. + */ + protected void selectNextPossibleValue() + { + int index = comboBox.getSelectedIndex(); + if (index != comboBox.getItemCount() - 1) + comboBox.setSelectedIndex(index + 1); + } + + /** + * Selects previous item relative to current selection to be + * next selected item. + */ + protected void selectPreviousPossibleValue() + { + int index = comboBox.getSelectedIndex(); + if (index > 0) + comboBox.setSelectedIndex(index - 1); + } + + /** + * Displays combo box popup if the popup is not currently shown + * on the screen and hides it if it is currently shown + */ + protected void toggleOpenClose() + { + setPopupVisible(comboBox, ! isPopupVisible(comboBox)); + } + + /** + * Returns the bounds in which comboBox's selected item will be + * displayed. + * + * @return rectangle bounds in which comboBox's selected Item will be + * displayed + */ + protected Rectangle rectangleForCurrentValue() + { + int w = comboBox.getWidth(); + int h = comboBox.getHeight(); + Insets i = comboBox.getInsets(); + int arrowSize = h - (i.top + i.bottom); + if (arrowButton != null) + arrowSize = arrowButton.getWidth(); + return new Rectangle(i.left, i.top, w - (i.left + i.right + arrowSize), + h - (i.top + i.left)); + } + + /** + * Returns the insets of the current border. + * + * @return Insets representing space between combo box and its border + */ + protected Insets getInsets() + { + return comboBox.getInsets(); + } + + /** + * Paints currently selected value in the main part of the combo + * box (part without popup). + * + * @param g graphics context + * @param bounds Rectangle representing the size of the area in which + * selected item should be drawn + * @param hasFocus true if combo box has focus and false otherwise + */ + public void paintCurrentValue(Graphics g, Rectangle bounds, boolean hasFocus) + { + /* Gets the component to be drawn for the current value. + * If there is currently no selected item we will take an empty + * String as replacement. + */ + ListCellRenderer renderer = comboBox.getRenderer(); + if (comboBox.getSelectedIndex() != -1) + { + Component comp; + if (hasFocus && ! isPopupVisible(comboBox)) + { + comp = renderer.getListCellRendererComponent(listBox, + comboBox.getSelectedItem(), -1, true, false); + } + else + { + comp = renderer.getListCellRendererComponent(listBox, + comboBox.getSelectedItem(), -1, false, false); + Color bg = UIManager.getColor("ComboBox.disabledForeground"); + comp.setBackground(bg); + } + comp.setFont(comboBox.getFont()); + if (hasFocus && ! isPopupVisible(comboBox)) + { + comp.setForeground(listBox.getSelectionForeground()); + comp.setBackground(listBox.getSelectionBackground()); + } + else if (comboBox.isEnabled()) + { + comp.setForeground(comboBox.getForeground()); + comp.setBackground(comboBox.getBackground()); + } + else + { + Color fg = UIManager.getColor("ComboBox.disabledForeground"); + comp.setForeground(fg); + Color bg = UIManager.getColor("ComboBox.disabledBackground"); + comp.setBackground(bg); + } + currentValuePane.paintComponent(g, comp, comboBox, bounds.x, bounds.y, + bounds.width, bounds.height); + } + } + + /** + * Paints the background of part of the combo box, where currently + * selected value is displayed. If the combo box has focus this method + * should also paint focus rectangle around the combo box. + * + * @param g graphics context + * @param bounds Rectangle representing the size of the largest item in the + * comboBox + * @param hasFocus true if combo box has fox and false otherwise + */ + public void paintCurrentValueBackground(Graphics g, Rectangle bounds, + boolean hasFocus) + { + Color saved = g.getColor(); + if (comboBox.isEnabled()) + g.setColor(UIManager.getColor("UIManager.background")); + else + g.setColor(UIManager.getColor("UIManager.disabledBackground")); + g.fillRect(bounds.x, bounds.y, bounds.width, bounds.height); + g.setColor(saved); + } + + private static final ListCellRenderer DEFAULT_RENDERER + = new DefaultListCellRenderer(); + + /** + * Returns the default size for the display area of a combo box that does + * not contain any elements. This method returns the width and height of + * a single space in the current font, plus a margin of 1 pixel. + * + * @return The default display size. + * + * @see #getDisplaySize() + */ + protected Dimension getDefaultSize() + { + Component comp = DEFAULT_RENDERER.getListCellRendererComponent(listBox, + " ", -1, false, false); + currentValuePane.add(comp); + comp.setFont(comboBox.getFont()); + Dimension d = comp.getPreferredSize(); + currentValuePane.remove(comp); + return d; + } + + /** + * Returns the size of the display area for the combo box. This size will be + * the size of the combo box, not including the arrowButton. + * + * @return The size of the display area for the combo box. + */ + protected Dimension getDisplaySize() + { + Dimension dim = new Dimension(); + ListCellRenderer renderer = comboBox.getRenderer(); + if (renderer == null) + { + renderer = DEFAULT_RENDERER; + } + + Object prototype = comboBox.getPrototypeDisplayValue(); + if (prototype != null) + { + Component comp = renderer.getListCellRendererComponent(listBox, + prototype, -1, false, false); + currentValuePane.add(comp); + comp.setFont(comboBox.getFont()); + Dimension renderSize = comp.getPreferredSize(); + currentValuePane.remove(comp); + dim.height = renderSize.height; + dim.width = renderSize.width; + } + else + { + ComboBoxModel model = comboBox.getModel(); + int size = model.getSize(); + if (size > 0) + { + for (int i = 0; i < size; ++i) + { + Component comp = renderer.getListCellRendererComponent(listBox, + model.getElementAt(i), -1, false, false); + currentValuePane.add(comp); + comp.setFont(comboBox.getFont()); + Dimension renderSize = comp.getPreferredSize(); + currentValuePane.remove(comp); + dim.width = Math.max(dim.width, renderSize.width); + dim.height = Math.max(dim.height, renderSize.height); + } + } + else + { + dim = getDefaultSize(); + if (comboBox.isEditable()) + dim.width = 100; + } + } + if (comboBox.isEditable()) + { + Dimension editSize = editor.getPreferredSize(); + dim.width = Math.max(dim.width, editSize.width); + dim.height = Math.max(dim.height, editSize.height); + } + displaySize.setSize(dim.width, dim.height); + return dim; + } + + /** + * Installs the keyboard actions for the {@link JComboBox} as specified + * by the look and feel. + */ + protected void installKeyboardActions() + { + SwingUtilities.replaceUIInputMap(comboBox, + JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, + (InputMap) UIManager.get("ComboBox.ancestorInputMap")); + // Install any action maps here. + } + + /** + * Uninstalls the keyboard actions for the {@link JComboBox} there were + * installed by in {@link #installListeners}. + */ + protected void uninstallKeyboardActions() + { + SwingUtilities.replaceUIInputMap(comboBox, + JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, null); + // Uninstall any action maps here. + } + + /** + * A {@link LayoutManager} used to position the sub-components of the + * {@link JComboBox}. + * + * @see BasicComboBoxUI#createLayoutManager() + */ + public class ComboBoxLayoutManager implements LayoutManager + { + /** + * Creates a new ComboBoxLayoutManager object. + */ + public ComboBoxLayoutManager() + { + // Nothing to do here. + } + + /** + * Adds a component to the layout. This method does nothing, since the + * layout manager doesn't need to track the components. + * + * @param name the name to associate the component with (ignored). + * @param comp the component (ignored). + */ + public void addLayoutComponent(String name, Component comp) + { + // Do nothing + } + + /** + * Removes a component from the layout. This method does nothing, since + * the layout manager doesn't need to track the components. + * + * @param comp the component. + */ + public void removeLayoutComponent(Component comp) + { + // Do nothing + } + + /** + * Returns preferred layout size of the JComboBox. + * + * @param parent the Container for which the preferred size should be + * calculated. + * + * @return The preferred size for the given container + */ + public Dimension preferredLayoutSize(Container parent) + { + return parent.getPreferredSize(); + } + + /** + * Returns the minimum layout size. + * + * @param parent the container. + * + * @return The minimum size. + */ + public Dimension minimumLayoutSize(Container parent) + { + return parent.getMinimumSize(); + } + + /** + * Arranges the components in the container. It puts arrow + * button right end part of the comboBox. If the comboBox is editable + * then editor is placed to the left of arrow button, starting from the + * beginning. + * + * @param parent Container that should be layed out. + */ + public void layoutContainer(Container parent) + { + // Position editor component to the left of arrow button if combo box is + // editable + Insets i = getInsets(); + int arrowSize = comboBox.getHeight() - (i.top + i.bottom); + + if (arrowButton != null) + arrowButton.setBounds(comboBox.getWidth() - (i.right + arrowSize), + i.top, arrowSize, arrowSize); + if (editor != null) + editor.setBounds(rectangleForCurrentValue()); + } + } + + /** + * Handles focus changes occuring in the combo box. This class is + * responsible for repainting combo box whenever focus is gained or lost + * and also for hiding popup list of items whenever combo box loses its + * focus. + */ + public class FocusHandler extends Object implements FocusListener + { + /** + * Creates a new FocusHandler object. + */ + public FocusHandler() + { + // Nothing to do here. + } + + /** + * Invoked when combo box gains focus. It repaints main + * part of combo box accordingly. + * + * @param e the FocusEvent + */ + public void focusGained(FocusEvent e) + { + hasFocus = true; + comboBox.repaint(); + } + + /** + * Invoked when the combo box loses focus. It repaints the main part + * of the combo box accordingly and hides the popup list of items. + * + * @param e the FocusEvent + */ + public void focusLost(FocusEvent e) + { + hasFocus = false; + if (! e.isTemporary() && comboBox.isLightWeightPopupEnabled()) + setPopupVisible(comboBox, false); + comboBox.repaint(); + } + } + + /** + * Handles {@link ItemEvent}s fired by the {@link JComboBox} when its + * selected item changes. + */ + public class ItemHandler extends Object implements ItemListener + { + /** + * Creates a new ItemHandler object. + */ + public ItemHandler() + { + // Nothing to do here. + } + + /** + * Invoked when selected item becomes deselected or when + * new item becomes selected. + * + * @param e the ItemEvent representing item's state change. + */ + public void itemStateChanged(ItemEvent e) + { + ComboBoxModel model = comboBox.getModel(); + Object v = model.getSelectedItem(); + if (editor != null) + comboBox.configureEditor(comboBox.getEditor(), v); + comboBox.repaint(); + } + } + + /** + * KeyHandler handles key events occuring while JComboBox has focus. + */ + public class KeyHandler extends KeyAdapter + { + public KeyHandler() + { + // Nothing to do here. + } + + /** + * Invoked whenever key is pressed while JComboBox is in focus. + */ + public void keyPressed(KeyEvent e) + { + if (comboBox.getModel().getSize() != 0 && comboBox.isEnabled()) + { + if (! isNavigationKey(e.getKeyCode())) + { + if (! comboBox.isEditable()) + if (comboBox.selectWithKeyChar(e.getKeyChar())) + e.consume(); + } + else + { + if (e.getKeyCode() == KeyEvent.VK_UP && comboBox.isPopupVisible()) + selectPreviousPossibleValue(); + else if (e.getKeyCode() == KeyEvent.VK_DOWN) + { + if (comboBox.isPopupVisible()) + selectNextPossibleValue(); + else + comboBox.showPopup(); + } + else if (e.getKeyCode() == KeyEvent.VK_ENTER + || e.getKeyCode() == KeyEvent.VK_ESCAPE) + popup.hide(); + } + } + } + } + + /** + * Handles the changes occurring in the JComboBox's data model. + */ + public class ListDataHandler extends Object implements ListDataListener + { + /** + * Creates a new ListDataHandler object. + */ + public ListDataHandler() + { + // Nothing to do here. + } + + /** + * Invoked if the content's of JComboBox's data model are changed. + * + * @param e ListDataEvent describing the change. + */ + public void contentsChanged(ListDataEvent e) + { + if (e.getIndex0() != -1 || e.getIndex1() != -1) + { + isMinimumSizeDirty = true; + comboBox.revalidate(); + } + if (editor != null) + comboBox.configureEditor(comboBox.getEditor(), + comboBox.getSelectedItem()); + comboBox.repaint(); + } + + /** + * Invoked when items are added to the JComboBox's data model. + * + * @param e ListDataEvent describing the change. + */ + public void intervalAdded(ListDataEvent e) + { + int start = e.getIndex0(); + int end = e.getIndex1(); + if (start == 0 && comboBox.getItemCount() - (end - start + 1) == 0) + contentsChanged(e); + else if (start != -1 || end != -1) + { + ListCellRenderer renderer = comboBox.getRenderer(); + ComboBoxModel model = comboBox.getModel(); + int w = displaySize.width; + int h = displaySize.height; + // TODO: Optimize using prototype here. + for (int i = start; i <= end; ++i) + { + Component comp = renderer.getListCellRendererComponent(listBox, + model.getElementAt(i), -1, false, false); + currentValuePane.add(comp); + comp.setFont(comboBox.getFont()); + Dimension dim = comp.getPreferredSize(); + w = Math.max(w, dim.width); + h = Math.max(h, dim.height); + currentValuePane.remove(comp); + } + if (displaySize.width < w || displaySize.height < h) + { + if (displaySize.width < w) + displaySize.width = w; + if (displaySize.height < h) + displaySize.height = h; + comboBox.revalidate(); + if (editor != null) + { + comboBox.configureEditor(comboBox.getEditor(), + comboBox.getSelectedItem()); + } + } + } + + } + + /** + * Invoked when items are removed from the JComboBox's + * data model. + * + * @param e ListDataEvent describing the change. + */ + public void intervalRemoved(ListDataEvent e) + { + contentsChanged(e); + } + } + + /** + * Handles {@link PropertyChangeEvent}s fired by the {@link JComboBox}. + */ + public class PropertyChangeHandler extends Object + implements PropertyChangeListener + { + /** + * Creates a new instance. + */ + public PropertyChangeHandler() + { + // Nothing to do here. + } + + /** + * Invoked whenever bound property of JComboBox changes. + * + * @param e the event. + */ + public void propertyChange(PropertyChangeEvent e) + { + // Lets assume every change invalidates the minimumsize. + String propName = e.getPropertyName(); + if (propName.equals("enabled")) + { + boolean enabled = comboBox.isEnabled(); + if (editor != null) + editor.setEnabled(enabled); + if (arrowButton != null) + arrowButton.setEnabled(enabled); + + comboBox.repaint(); + } + else if (propName.equals("editor") && comboBox.isEditable()) + { + addEditor(); + comboBox.revalidate(); + } + else if (e.getPropertyName().equals("editable")) + { + if (comboBox.isEditable()) + { + addEditor(); + } + else + { + removeEditor(); + } + + comboBox.revalidate(); + } + else if (propName.equals("model")) + { + // remove ListDataListener from old model and add it to new model + ComboBoxModel oldModel = (ComboBoxModel) e.getOldValue(); + if (oldModel != null && listDataListener != null) + oldModel.removeListDataListener(listDataListener); + + ComboBoxModel newModel = (ComboBoxModel) e.getNewValue(); + if (newModel != null && listDataListener != null) + comboBox.getModel().addListDataListener(listDataListener); + + if (editor != null) + { + comboBox.configureEditor(comboBox.getEditor(), + comboBox.getSelectedItem()); + } + isMinimumSizeDirty = true; + comboBox.revalidate(); + comboBox.repaint(); + } + else if (propName.equals("font")) + { + Font font = (Font) e.getNewValue(); + if (editor != null) + { + editor.setFont(font); + } + listBox.setFont(font); + isMinimumSizeDirty = true; + comboBox.revalidate(); + } + else if (propName.equals("prototypeDisplayValue")) + { + isMinimumSizeDirty = true; + comboBox.revalidate(); + } + else if (propName.equals("renderer")) + { + isMinimumSizeDirty = true; + comboBox.revalidate(); + } + // FIXME: Need to handle changes in other bound properties. + } + } +} diff --git a/libjava/classpath/javax/swing/plaf/basic/BasicComboPopup.java b/libjava/classpath/javax/swing/plaf/basic/BasicComboPopup.java new file mode 100644 index 000000000..3e54ca7c6 --- /dev/null +++ b/libjava/classpath/javax/swing/plaf/basic/BasicComboPopup.java @@ -0,0 +1,1104 @@ +/* BasicComboPopup.java -- + Copyright (C) 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 java.awt.Color; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.Insets; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.awt.event.MouseMotionAdapter; +import java.awt.event.MouseMotionListener; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; + +import javax.swing.BorderFactory; +import javax.swing.ComboBoxModel; +import javax.swing.JComboBox; +import javax.swing.JList; +import javax.swing.JPopupMenu; +import javax.swing.JScrollBar; +import javax.swing.JScrollPane; +import javax.swing.ListCellRenderer; +import javax.swing.ListSelectionModel; +import javax.swing.MenuSelectionManager; +import javax.swing.SwingConstants; +import javax.swing.SwingUtilities; +import javax.swing.Timer; +import javax.swing.UIManager; +import javax.swing.event.ListDataEvent; +import javax.swing.event.ListDataListener; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; +import javax.swing.event.PopupMenuEvent; +import javax.swing.event.PopupMenuListener; + +/** + * UI Delegate for ComboPopup + * + * @author Olga Rodimina + */ +public class BasicComboPopup extends JPopupMenu implements ComboPopup +{ + /* Timer for autoscrolling */ + protected Timer autoscrollTimer; + + /** ComboBox associated with this popup */ + protected JComboBox comboBox; + + /** FIXME: Need to document */ + protected boolean hasEntered; + + /** + * Indicates whether the scroll bar located in popup menu with comboBox's + * list of items is currently autoscrolling. This happens when mouse event + * originated in the combo box and is dragged outside of its bounds + */ + protected boolean isAutoScrolling; + + /** ItemListener listening to the selection changes in the combo box */ + protected ItemListener itemListener; + + /** This listener is not used */ + protected KeyListener keyListener; + + /** JList which is used to display item is the combo box */ + protected JList list; + + /** This listener is not used */ + protected ListDataListener listDataListener; + + /** + * MouseListener listening to mouse events occuring in the combo box's + * list. + */ + protected MouseListener listMouseListener; + + /** + * MouseMotionListener listening to mouse motion events occuring in the + * combo box's list + */ + protected MouseMotionListener listMouseMotionListener; + + /** This listener is not used */ + protected ListSelectionListener listSelectionListener; + + /** MouseListener listening to mouse events occuring in the combo box */ + protected MouseListener mouseListener; + + /** + * MouseMotionListener listening to mouse motion events occuring in the + * combo box + */ + protected MouseMotionListener mouseMotionListener; + + /** + * PropertyChangeListener listening to changes occuring in the bound + * properties of the combo box + */ + protected PropertyChangeListener propertyChangeListener; + + /** direction for scrolling down list of combo box's items */ + protected static final int SCROLL_DOWN = 1; + + /** direction for scrolling up list of combo box's items */ + protected static final int SCROLL_UP = 0; + + /** Indicates auto scrolling direction */ + protected int scrollDirection; + + /** JScrollPane that contains list portion of the combo box */ + protected JScrollPane scroller; + + /** This field is not used */ + protected boolean valueIsAdjusting; + + /** + * Creates a new BasicComboPopup object. + * + * @param comboBox the combo box with which this popup should be associated + */ + public BasicComboPopup(JComboBox comboBox) + { + this.comboBox = comboBox; + mouseListener = createMouseListener(); + mouseMotionListener = createMouseMotionListener(); + keyListener = createKeyListener(); + + list = createList(); + configureList(); + scroller = createScroller(); + configureScroller(); + configurePopup(); + installComboBoxListeners(); + installKeyboardActions(); + } + + /** + * This method displays drow down list of combo box items on the screen. + */ + public void show() + { + Dimension size = comboBox.getSize(); + size.height = getPopupHeightForRowCount(comboBox.getMaximumRowCount()); + Insets i = getInsets(); + size.width -= i.left + i.right; + Rectangle bounds = computePopupBounds(0, comboBox.getBounds().height, + size.width, size.height); + + scroller.setMaximumSize(bounds.getSize()); + scroller.setPreferredSize(bounds.getSize()); + scroller.setMinimumSize(bounds.getSize()); + list.invalidate(); + + syncListSelection(); + + list.ensureIndexIsVisible(list.getSelectedIndex()); + setLightWeightPopupEnabled(comboBox.isLightWeightPopupEnabled()); + show(comboBox, bounds.x, bounds.y); + } + + /** + * This method hides drop down list of items + */ + public void hide() + { + MenuSelectionManager menuSelectionManager = + MenuSelectionManager.defaultManager(); + javax.swing.MenuElement[] menuElements = + menuSelectionManager.getSelectedPath(); + for (int i = 0; i < menuElements.length; i++) + { + if (menuElements[i] == this) + { + menuSelectionManager.clearSelectedPath(); + break; + } + } + comboBox.repaint(); + } + + /** + * Return list cointaining JComboBox's items + * + * @return list cointaining JComboBox's items + */ + public JList getList() + { + return list; + } + + /** + * Returns MouseListener that is listening to mouse events occuring in the + * combo box. + * + * @return MouseListener + */ + public MouseListener getMouseListener() + { + return mouseListener; + } + + /** + * Returns MouseMotionListener that is listening to mouse motion events + * occuring in the combo box. + * + * @return MouseMotionListener + */ + public MouseMotionListener getMouseMotionListener() + { + return mouseMotionListener; + } + + /** + * Returns KeyListener listening to key events occuring in the combo box. + * This method returns null because KeyHandler is not longer used. + * + * @return KeyListener + */ + public KeyListener getKeyListener() + { + return keyListener; + } + + /** + * This method uninstalls the UI for the given JComponent. + */ + public void uninstallingUI() + { + if (propertyChangeListener != null) + { + comboBox.removePropertyChangeListener(propertyChangeListener); + } + if (itemListener != null) + { + comboBox.removeItemListener(itemListener); + } + uninstallComboBoxModelListeners(comboBox.getModel()); + uninstallKeyboardActions(); + uninstallListListeners(); + } + + /** + * This method uninstalls listeners that were listening to changes occuring + * in the comb box's data model + * + * @param model data model for the combo box from which to uninstall + * listeners + */ + protected void uninstallComboBoxModelListeners(ComboBoxModel model) + { + model.removeListDataListener(listDataListener); + } + + /** + * This method uninstalls keyboard actions installed by the UI. + */ + protected void uninstallKeyboardActions() + { + // Nothing to do here. + } + + /** + * This method fires PopupMenuEvent indicating that combo box's popup list + * of items will become visible + */ + protected void firePopupMenuWillBecomeVisible() + { + PopupMenuListener[] ll = comboBox.getPopupMenuListeners(); + + for (int i = 0; i < ll.length; i++) + ll[i].popupMenuWillBecomeVisible(new PopupMenuEvent(comboBox)); + } + + /** + * This method fires PopupMenuEvent indicating that combo box's popup list + * of items will become invisible. + */ + protected void firePopupMenuWillBecomeInvisible() + { + PopupMenuListener[] ll = comboBox.getPopupMenuListeners(); + + for (int i = 0; i < ll.length; i++) + ll[i].popupMenuWillBecomeInvisible(new PopupMenuEvent(comboBox)); + } + + /** + * This method fires PopupMenuEvent indicating that combo box's popup list + * of items was closed without selection. + */ + protected void firePopupMenuCanceled() + { + PopupMenuListener[] ll = comboBox.getPopupMenuListeners(); + + for (int i = 0; i < ll.length; i++) + ll[i].popupMenuCanceled(new PopupMenuEvent(comboBox)); + } + + /** + * Creates MouseListener to listen to mouse events occuring in the combo + * box. Note that this listener doesn't listen to mouse events occuring in + * the popup portion of the combo box, it only listens to main combo box + * part. + * + * @return new MouseMotionListener that listens to mouse events occuring in + * the combo box + */ + protected MouseListener createMouseListener() + { + return new InvocationMouseHandler(); + } + + /** + * Create Mouse listener that listens to mouse dragging events occuring in + * the combo box. This listener is responsible for changing the selection + * in the combo box list to the component over which mouse is being + * currently dragged + * + * @return new MouseMotionListener that listens to mouse dragging events + * occuring in the combo box + */ + protected MouseMotionListener createMouseMotionListener() + { + return new InvocationMouseMotionHandler(); + } + + /** + * KeyListener created in this method is not used anymore. + * + * @return KeyListener that does nothing + */ + protected KeyListener createKeyListener() + { + return new InvocationKeyHandler(); + } + + /** + * ListSelectionListener created in this method is not used anymore + * + * @return ListSelectionListener that does nothing + */ + protected ListSelectionListener createListSelectionListener() + { + return new ListSelectionHandler(); + } + + /** + * Creates ListDataListener. This method returns null, because + * ListDataHandler class is obsolete and is no longer used. + * + * @return null + */ + protected ListDataListener createListDataListener() + { + return null; + } + + /** + * This method creates ListMouseListener to listen to mouse events occuring + * in the combo box's item list. + * + * @return MouseListener to listen to mouse events occuring in the combo + * box's items list. + */ + protected MouseListener createListMouseListener() + { + return new ListMouseHandler(); + } + + /** + * Creates ListMouseMotionlistener to listen to mouse motion events occuring + * in the combo box's list. This listener is responsible for highlighting + * items in the list when mouse is moved over them. + * + * @return MouseMotionListener that handles mouse motion events occuring in + * the list of the combo box. + */ + protected MouseMotionListener createListMouseMotionListener() + { + return new ListMouseMotionHandler(); + } + + /** + * Creates PropertyChangeListener to handle changes in the JComboBox's bound + * properties. + * + * @return PropertyChangeListener to handle changes in the JComboBox's bound + * properties. + */ + protected PropertyChangeListener createPropertyChangeListener() + { + return new PropertyChangeHandler(); + } + + /** + * Creates new ItemListener that will listen to ItemEvents occuring in the + * combo box. + * + * @return ItemListener to listen to ItemEvents occuring in the combo box. + */ + protected ItemListener createItemListener() + { + return new ItemHandler(); + } + + /** + * Creates JList that will be used to display items in the combo box. + * + * @return JList that will be used to display items in the combo box. + */ + protected JList createList() + { + JList l = new JList(comboBox.getModel()); + return l; + } + + /** + * This method configures the list of comboBox's items by setting default + * properties and installing listeners. + */ + protected void configureList() + { + list.setFont(comboBox.getFont()); + list.setForeground(comboBox.getForeground()); + list.setBackground(comboBox.getBackground()); + Color sfg = UIManager.getColor("ComboBox.selectionForeground"); + list.setSelectionForeground(sfg); + Color sbg = UIManager.getColor("ComboBox.selectionBackground"); + list.setSelectionBackground(sbg); + list.setBorder(null); + list.setCellRenderer(comboBox.getRenderer()); + list.setFocusable(false); + list.setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION); + installListListeners(); + } + + /** + * This method installs list listeners. + */ + protected void installListListeners() + { + // mouse listener listening to mouse events occuring in the + // combo box's list of items. + listMouseListener = createListMouseListener(); + list.addMouseListener(listMouseListener); + + // mouse listener listening to mouse motion events occuring in the + // combo box's list of items + listMouseMotionListener = createListMouseMotionListener(); + list.addMouseMotionListener(listMouseMotionListener); + + listSelectionListener = createListSelectionListener(); + list.addListSelectionListener(listSelectionListener); + } + + /** + * This method creates scroll pane that will contain the list of comboBox's + * items inside of it. + * + * @return JScrollPane + */ + protected JScrollPane createScroller() + { + return new JScrollPane(list, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, + JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); + } + + /** + * This method configures scroll pane to contain list of comboBox's items + */ + protected void configureScroller() + { + scroller.setBorder(null); + scroller.setFocusable(false); + scroller.getVerticalScrollBar().setFocusable(false); + } + + /** + * This method configures popup menu that will be used to display Scrollpane + * with list of items inside of it. + */ + protected void configurePopup() + { + setBorderPainted(true); + setBorder(BorderFactory.createLineBorder(Color.BLACK)); + setOpaque(false); + add(scroller); + setFocusable(false); + } + + /* + * This method installs listeners that will listen to changes occuring + * in the combo box. + */ + protected void installComboBoxListeners() + { + // item listener listenening to selection events in the combo box + itemListener = createItemListener(); + comboBox.addItemListener(itemListener); + + propertyChangeListener = createPropertyChangeListener(); + comboBox.addPropertyChangeListener(propertyChangeListener); + + installComboBoxModelListeners(comboBox.getModel()); + } + + /** + * This method installs listeners that will listen to changes occuring in + * the comb box's data model + * + * @param model data model for the combo box for which to install listeners + */ + protected void installComboBoxModelListeners(ComboBoxModel model) + { + // list data listener to listen for ListDataEvents in combo box. + // This listener is now obsolete and nothing is done here + listDataListener = createListDataListener(); + comboBox.getModel().addListDataListener(listDataListener); + } + + /** + * Installs the keyboard actions. + */ + protected void installKeyboardActions() + { + // Nothing to do here + } + + /** + * This method always returns false to indicate that items in the combo box + * list are not focus traversable. + * + * @return false + */ + public boolean isFocusTraversable() + { + return false; + } + + /** + * This method start scrolling combo box's list of items either up or down + * depending on the specified 'direction' + * + * @param direction of the scrolling. + */ + protected void startAutoScrolling(int direction) + { + // FIXME: add timer + isAutoScrolling = true; + + if (direction == SCROLL_UP) + autoScrollUp(); + else + autoScrollDown(); + } + + /** + * This method stops scrolling the combo box's list of items + */ + protected void stopAutoScrolling() + { + // FIXME: add timer + isAutoScrolling = false; + } + + /** + * This method scrolls up list of combo box's items up and highlights that + * just became visible. + */ + protected void autoScrollUp() + { + // scroll up the scroll bar to make the item above visible + JScrollBar scrollbar = scroller.getVerticalScrollBar(); + int scrollToNext = list.getScrollableUnitIncrement(super.getBounds(), + SwingConstants.VERTICAL, + SCROLL_UP); + + scrollbar.setValue(scrollbar.getValue() - scrollToNext); + + // If we haven't reached the begging of the combo box's list of items, + // then highlight next element above currently highlighted element + if (list.getSelectedIndex() != 0) + list.setSelectedIndex(list.getSelectedIndex() - 1); + } + + /** + * This method scrolls down list of combo box's and highlights item in the + * list that just became visible. + */ + protected void autoScrollDown() + { + // scroll scrollbar down to make next item visible + JScrollBar scrollbar = scroller.getVerticalScrollBar(); + int scrollToNext = list.getScrollableUnitIncrement(super.getBounds(), + SwingConstants.VERTICAL, + SCROLL_DOWN); + scrollbar.setValue(scrollbar.getValue() + scrollToNext); + + // If we haven't reached the end of the combo box's list of items + // then highlight next element below currently highlighted element + if (list.getSelectedIndex() + 1 != comboBox.getItemCount()) + list.setSelectedIndex(list.getSelectedIndex() + 1); + } + + /** + * This method helps to delegate focus to the right component in the + * JComboBox. If the comboBox is editable then focus is sent to + * ComboBoxEditor, otherwise it is delegated to JComboBox. + * + * @param e MouseEvent + */ + protected void delegateFocus(MouseEvent e) + { + if (comboBox.isEditable()) + comboBox.getEditor().getEditorComponent().requestFocus(); + else + comboBox.requestFocus(); + } + + /** + * This method displays combo box popup if the popup is not currently shown + * on the screen and hides it if it is currently visible + */ + protected void togglePopup() + { + if (isVisible()) + hide(); + else + show(); + } + + /** + * DOCUMENT ME! + * + * @param e DOCUMENT ME! + * + * @return DOCUMENT ME! + */ + protected MouseEvent convertMouseEvent(MouseEvent e) + { + Point point = SwingUtilities.convertPoint((Component) e.getSource(), + e.getPoint(), list); + MouseEvent newEvent = new MouseEvent((Component) e.getSource(), + e.getID(), e.getWhen(), + e.getModifiers(), point.x, point.y, + e.getModifiers(), + e.isPopupTrigger()); + return newEvent; + } + + /** + * Returns required height of the popup such that number of items visible in + * it are equal to the maximum row count. By default + * comboBox.maximumRowCount=8 + * + * @param maxRowCount number of maximum visible rows in the combo box's + * popup list of items + * + * @return height of the popup required to fit number of items equal to + * JComboBox.maximumRowCount. + */ + protected int getPopupHeightForRowCount(int maxRowCount) + { + int totalHeight = 0; + ListCellRenderer rend = list.getCellRenderer(); + + if (comboBox.getItemCount() < maxRowCount) + maxRowCount = comboBox.getItemCount(); + + for (int i = 0; i < maxRowCount; i++) + { + Component comp = rend.getListCellRendererComponent(list, + comboBox.getModel() + .getElementAt(i), + -1, false, false); + Dimension dim = comp.getPreferredSize(); + totalHeight += dim.height; + } + + return totalHeight == 0 ? 100 : totalHeight; + } + + /** + * DOCUMENT ME! + * + * @param px DOCUMENT ME! + * @param py DOCUMENT ME! + * @param pw DOCUMENT ME! + * @param ph DOCUMENT ME! + * + * @return DOCUMENT ME! + */ + protected Rectangle computePopupBounds(int px, int py, int pw, int ph) + { + return new Rectangle(px, py, pw, ph); + } + + /** + * This method changes the selection in the list to the item over which the + * mouse is currently located. + * + * @param anEvent MouseEvent + * @param shouldScroll DOCUMENT ME! + */ + protected void updateListBoxSelectionForEvent(MouseEvent anEvent, + boolean shouldScroll) + { + Point point = anEvent.getPoint(); + if (list != null) + { + int index = list.locationToIndex(point); + if (index == -1) + { + if (point.y < 0) + index = 0; + else + index = comboBox.getModel().getSize() - 1; + } + if (list.getSelectedIndex() != index) + { + list.setSelectedIndex(index); + if (shouldScroll) + list.ensureIndexIsVisible(index); + } + } + } + + /** + * InvocationMouseHandler is a listener that listens to mouse events + * occuring in the combo box. Note that this listener doesn't listen to + * mouse events occuring in the popup portion of the combo box, it only + * listens to main combo box part(area that displays selected item). This + * listener is responsible for showing and hiding popup portion of the + * combo box. + */ + protected class InvocationMouseHandler extends MouseAdapter + { + /** + * Creates a new InvocationMouseHandler object. + */ + protected InvocationMouseHandler() + { + // Nothing to do here. + } + + /** + * This method is invoked whenever mouse is being pressed over the main + * part of the combo box. This method will show popup if the popup is + * not shown on the screen right now, and it will hide popup otherwise. + * + * @param e MouseEvent that should be handled + */ + public void mousePressed(MouseEvent e) + { + if (SwingUtilities.isLeftMouseButton(e) && comboBox.isEnabled()) + { + delegateFocus(e); + togglePopup(); + } + } + + /** + * This method is invoked whenever mouse event was originated in the combo + * box and released either in the combBox list of items or in the combo + * box itself. + * + * @param e MouseEvent that should be handled + */ + public void mouseReleased(MouseEvent e) + { + Component component = (Component) e.getSource(); + Dimension size = component.getSize(); + Rectangle bounds = new Rectangle(0, 0, size.width - 1, size.height - 1); + // If mouse was released inside the bounds of combo box then do nothing, + // Otherwise if mouse was released inside the list of combo box items + // then change selection and close popup + if (! bounds.contains(e.getPoint())) + { + MouseEvent convEvent = convertMouseEvent(e); + Point point = convEvent.getPoint(); + Rectangle visRect = new Rectangle(); + list.computeVisibleRect(visRect); + if (visRect.contains(point)) + { + updateListBoxSelectionForEvent(convEvent, false); + comboBox.setSelectedIndex(list.getSelectedIndex()); + } + hide(); + } + hasEntered = false; + stopAutoScrolling(); + } + } + + /** + * InvocationMouseMotionListener is a mouse listener that listens to mouse + * dragging events occuring in the combo box. + */ + protected class InvocationMouseMotionHandler extends MouseMotionAdapter + { + /** + * Creates a new InvocationMouseMotionHandler object. + */ + protected InvocationMouseMotionHandler() + { + // Nothing to do here. + } + + /** + * This method is responsible for highlighting item in the drop down list + * over which the mouse is currently being dragged. + */ + public void mouseDragged(MouseEvent e) + { + if (isVisible()) + { + MouseEvent convEvent = convertMouseEvent(e); + Rectangle visRect = new Rectangle(); + list.computeVisibleRect(visRect); + if (convEvent.getPoint().y >= visRect.y + && (convEvent.getPoint().y <= visRect.y + visRect.height - 1)) + { + hasEntered = true; + if (isAutoScrolling) + stopAutoScrolling(); + Point point = convEvent.getPoint(); + if (visRect.contains(point)) + { + valueIsAdjusting = true; + updateListBoxSelectionForEvent(convEvent, false); + valueIsAdjusting = false; + } + } + else if (hasEntered) + { + int dir = convEvent.getPoint().y < visRect.y ? SCROLL_UP + : SCROLL_DOWN; + if (isAutoScrolling && scrollDirection != dir) + { + stopAutoScrolling(); + startAutoScrolling(dir); + } + else if (!isAutoScrolling) + startAutoScrolling(dir); + } + else if (e.getPoint().y < 0) + { + hasEntered = true; + startAutoScrolling(SCROLL_UP); + } + } + } + } + + /** + * ItemHandler is an item listener that listens to selection events occuring + * in the combo box. FIXME: should specify here what it does when item is + * selected or deselected in the combo box list. + */ + protected class ItemHandler extends Object implements ItemListener + { + /** + * Creates a new ItemHandler object. + */ + protected ItemHandler() + { + // Nothing to do here. + } + + /** + * This method responds to the selection events occuring in the combo box. + * + * @param e ItemEvent specifying the combo box's selection + */ + public void itemStateChanged(ItemEvent e) + { + if (e.getStateChange() == ItemEvent.SELECTED && ! valueIsAdjusting) + { + valueIsAdjusting = true; + syncListSelection(); + valueIsAdjusting = false; + list.ensureIndexIsVisible(comboBox.getSelectedIndex()); + } + } + } + + /** + * ListMouseHandler is a listener that listens to mouse events occuring in + * the combo box's list of items. This class is responsible for hiding + * popup portion of the combo box if the mouse is released inside the combo + * box's list. + */ + protected class ListMouseHandler extends MouseAdapter + { + protected ListMouseHandler() + { + // Nothing to do here. + } + + public void mousePressed(MouseEvent e) + { + // Nothing to do here. + } + + public void mouseReleased(MouseEvent anEvent) + { + comboBox.setSelectedIndex(list.getSelectedIndex()); + hide(); + } + } + + /** + * ListMouseMotionHandler listens to mouse motion events occuring in the + * combo box's list. This class is responsible for highlighting items in + * the list when mouse is moved over them + */ + protected class ListMouseMotionHandler extends MouseMotionAdapter + { + protected ListMouseMotionHandler() + { + // Nothing to do here. + } + + public void mouseMoved(MouseEvent anEvent) + { + Point point = anEvent.getPoint(); + Rectangle visRect = new Rectangle(); + list.computeVisibleRect(visRect); + if (visRect.contains(point)) + { + valueIsAdjusting = true; + updateListBoxSelectionForEvent(anEvent, false); + valueIsAdjusting = false; + } + } + } + + /** + * This class listens to changes occuring in the bound properties of the + * combo box + */ + protected class PropertyChangeHandler extends Object + implements PropertyChangeListener + { + protected PropertyChangeHandler() + { + // Nothing to do here. + } + + public void propertyChange(PropertyChangeEvent e) + { + if (e.getPropertyName().equals("renderer")) + { + list.setCellRenderer(comboBox.getRenderer()); + if (isVisible()) + hide(); + } + if (e.getPropertyName().equals("model")) + { + ComboBoxModel oldModel = (ComboBoxModel) e.getOldValue(); + uninstallComboBoxModelListeners(oldModel); + ComboBoxModel newModel = (ComboBoxModel) e.getNewValue(); + list.setModel(newModel); + installComboBoxModelListeners(newModel); + if (comboBox.getItemCount() > 0) + comboBox.setSelectedIndex(0); + if (isVisible()) + hide(); + } + } + } + + // ------ private helper methods -------------------- + + /** + * This method uninstalls Listeners registered with combo boxes list of + * items + */ + private void uninstallListListeners() + { + list.removeMouseListener(listMouseListener); + listMouseListener = null; + + list.removeMouseMotionListener(listMouseMotionListener); + listMouseMotionListener = null; + } + + void syncListSelection() + { + int index = comboBox.getSelectedIndex(); + if (index == -1) + list.clearSelection(); + else + list.setSelectedIndex(index); + } + + // -------------------------------------------------------------------- + // The following classes are here only for backwards API compatibility + // They aren't used. + // -------------------------------------------------------------------- + + /** + * This class is not used any more. + */ + public class ListDataHandler extends Object implements ListDataListener + { + public ListDataHandler() + { + // Nothing to do here. + } + + public void contentsChanged(ListDataEvent e) + { + // Nothing to do here. + } + + public void intervalAdded(ListDataEvent e) + { + // Nothing to do here. + } + + public void intervalRemoved(ListDataEvent e) + { + // Nothing to do here. + } + } + + /** + * This class is not used anymore + */ + protected class ListSelectionHandler extends Object + implements ListSelectionListener + { + protected ListSelectionHandler() + { + // Nothing to do here. + } + + public void valueChanged(ListSelectionEvent e) + { + // Nothing to do here. + } + } + + /** + * This class is not used anymore + */ + public class InvocationKeyHandler extends KeyAdapter + { + public InvocationKeyHandler() + { + // Nothing to do here. + } + + public void keyReleased(KeyEvent e) + { + // Nothing to do here. + } + } +} diff --git a/libjava/classpath/javax/swing/plaf/basic/BasicDesktopIconUI.java b/libjava/classpath/javax/swing/plaf/basic/BasicDesktopIconUI.java new file mode 100644 index 000000000..e52293812 --- /dev/null +++ b/libjava/classpath/javax/swing/plaf/basic/BasicDesktopIconUI.java @@ -0,0 +1,592 @@ +/* BasicDesktopIconUI.java -- + Copyright (C) 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 java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Insets; +import java.awt.Rectangle; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.MouseEvent; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.beans.PropertyVetoException; + +import javax.swing.Icon; +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JDesktopPane; +import javax.swing.JInternalFrame; +import javax.swing.JInternalFrame.JDesktopIcon; +import javax.swing.SwingConstants; +import javax.swing.border.Border; +import javax.swing.event.MouseInputAdapter; +import javax.swing.event.MouseInputListener; +import javax.swing.plaf.ComponentUI; +import javax.swing.plaf.DesktopIconUI; + +/** + * This class acts as the UI delegate for JDesktopIcons for the Basic look and feel. + */ +public class BasicDesktopIconUI extends DesktopIconUI +{ + /** + * This helper class handles mouse events that occur on the JDesktopIcon. + */ + public class MouseInputHandler extends MouseInputAdapter + { + /** The x offset from the MouseEvent coordinates to the top left corner. */ + private transient int xOffset; + + /** The y offset fromt he MouseEvent coordinates to the top left corner. */ + private transient int yOffset; + + /** A cached value of the JDesktopPane that parents this JDesktopIcon. */ + private transient JDesktopPane pane; + + /** + * This method is called when the mouse is dragged in the JDesktopIcon. + * + * @param e The MouseEvent. + */ + public void mouseDragged(MouseEvent e) + { + Rectangle b = desktopIcon.getBounds(); + + moveAndRepaint(desktopIcon, b.x + e.getX() - xOffset, + b.y + e.getY() - yOffset, b.width, b.height); + } + + /** + * This method is called when the mouse is moved in the JDesktopIcon. + * + * @param e The MouseEvent. + */ + public void mouseMoved(MouseEvent e) + { + // Nothing to do. + } + + /** + * This method is called when the mouse is pressed in the JDesktopIcon. + * + * @param e The MouseEvent. + */ + public void mousePressed(MouseEvent e) + { + xOffset = e.getX(); + yOffset = e.getY(); + pane = frame.getDesktopPane(); + if (pane != null) + pane.getDesktopManager().beginDraggingFrame(desktopIcon); + } + + /** + * This method is called when the mouse is released in the JDesktopIcon. + * + * @param e The MouseEvent. + */ + public void mouseReleased(MouseEvent e) + { + if (pane != null) + pane.getDesktopManager().endDraggingFrame(desktopIcon); + xOffset = 0; + yOffset = 0; + } + + /** + * This method moves and repaints the JDesktopIcon to the given bounds. + * + * @param f The JComponent to move and repaint. + * @param newX The new x coordinate. + * @param newY The new y coordinate. + * @param newWidth The new width. + * @param newHeight The new height. + */ + public void moveAndRepaint(JComponent f, int newX, int newY, int newWidth, + int newHeight) + { + if (pane != null) + pane.getDesktopManager().dragFrame(f, newX, newY); + else + desktopIcon.setBounds(newX, newY, newWidth, newHeight); + } + } + + /** + * This class acts as the border for the JDesktopIcon. + */ + private class DesktopIconBorder implements Border + { + /** The left inset value. */ + int left = 10; + + /** The top inset value. */ + int top = 4; + + /** The right inset value. */ + int right = top; + + /** The bottom inset value. */ + int bottom = top; + + /** + * This method returns the insets of the border. + * + * @param c The Component to find border insets for. + * + * @return The border insets. + */ + public Insets getBorderInsets(Component c) + { + return new Insets(top, left, bottom, right); + } + + /** + * This method returns whether the border is opaque. + * + * @return Whether the border is opaque. + */ + public boolean isBorderOpaque() + { + return true; + } + + /** + * This method paints the border. + * + * @param c The Component the border is in. + * @param g The Graphics object to paint with. + * @param x The x coordinate of the Component. + * @param y The y coordinate of the Component. + * @param width The width of the Component. + * @param height The height of the Component. + */ + public void paintBorder(Component c, Graphics g, int x, int y, int width, + int height) + { + g.translate(x, y); + Color saved = g.getColor(); + + g.setColor(Color.LIGHT_GRAY); + + g.fillRect(0, 0, left, height); + g.fillRect(0, 0, width, top); + g.fillRect(0, height - bottom, width, bottom); + g.fillRect(width - right, 0, right, height); + + g.setColor(Color.BLACK); + g.drawRect(0, 0, width - 1, height - 1); + + int fHeight = height / 4; + int hLeft = left / 2; + + g.setColor(Color.BLACK); + g.fillRect(hLeft, fHeight, 2, 2); + g.fillRect(hLeft, fHeight * 2, 2, 2); + g.fillRect(hLeft, fHeight * 3, 2, 2); + + g.setColor(saved); + g.translate(-x, -y); + } + } + + /** The static width and height of the iconSize. */ + private static final int iconSize = 16; + + /** + * This class represents the default frame icon when none + * is supplied by the JInternalFrame. + */ + static class InternalFrameDefaultMenuIcon implements Icon + { + /** + * This returns the icon height. + * + * @return The icon height. + */ + public int getIconHeight() + { + return iconSize; + } + + /** + * This returns the icon width. + * + * @return The icon width. + */ + public int getIconWidth() + { + return iconSize; + } + + /** + * This method paints the icon. + * + * @param c The Component this icon belongs to. + * @param g The Graphics object to paint with. + * @param x The x coordinate to paint at. + * @param y The y coordinate to paint at. + */ + public void paintIcon(Component c, Graphics g, int x, int y) + { + g.translate(x, y); + Color saved = g.getColor(); + + g.setColor(Color.BLUE); + g.fillRect(0, 0, iconSize, (int) ((double) iconSize / 3) + 1); + + g.setColor(Color.WHITE); + g.fillRect(0, (int) ((double) iconSize / 3), iconSize, iconSize * 5 / 6); + + g.setColor(Color.GRAY); + g.drawRect(0, 0, iconSize, iconSize); + + g.setColor(saved); + g.translate(-x, -y); + } + } + + /** The default JDesktopIcon width. */ + private static final int iconWidth = 160; + + /** The default JDesktopIcon height */ + private static final int iconHeight = 35; + + /** The JDesktopIcon this UI delegate represents. */ + protected JDesktopIcon desktopIcon; + + /** The JInternalFrame associated with the JDesktopIcon. */ + protected JInternalFrame frame; + + /** The MouseListener responsible for reacting to MouseEvents on the JDesktopIcon. */ + private transient MouseInputListener mouseHandler; + + /** The Button in the JDesktopIcon responsible for deiconifying it. + * This is package-private to avoid an accessor method. */ + transient BoundButton button; + + /** The PropertyChangeListener listening to the JDesktopIcon. */ + private transient PropertyChangeListener propertyHandler; + + /** The default icon used when no frame icon is given by the JInternalFrame. */ + static Icon defaultIcon = new InternalFrameDefaultMenuIcon(); + + /** + * This is a helper class that is used in JDesktopIcon and gives the Button a predetermined size. + */ + private class BoundButton extends JButton + { + /** + * Creates a new BoundButton object. + * + * @param title The title of the button. + */ + public BoundButton(String title) + { + super(title); + } + + /** + * This method returns a standard size (based on the defaults of the JDesktopIcon) and the insets. + * + * @return The preferred size of the JDesktopIcon. + */ + public Dimension getPreferredSize() + { + Insets insets = desktopIcon.getInsets(); + return new Dimension(iconWidth - insets.left - insets.right, + iconHeight - insets.top - insets.bottom); + } + + /** + * This method returns the minimum size of the button. + * + * @return The minimum size of the button. + */ + public Dimension getMinimumSize() + { + return getPreferredSize(); + } + + /** + * This method returns the maximum size of the button. + * + * @return The maximum size of the button. + */ + public Dimension getMaximumSize() + { + return getPreferredSize(); + } + } + + /** + * Creates a new BasicDesktopIconUI object. + */ + public BasicDesktopIconUI() + { + // Nothing to do here. + } + + /** + * This method creates a new BasicDesktopIconUI for the given JComponent. + * + * @param c The JComponent to create a UI for. + * + * @return A new BasicDesktopIconUI. + */ + public static ComponentUI createUI(JComponent c) + { + return new BasicDesktopIconUI(); + } + + /** + * This method installs the UI for the given JComponent. + * + * @param c The JComponent to install this UI for. + */ + public void installUI(JComponent c) + { + if (c instanceof JDesktopIcon) + { + desktopIcon = (JDesktopIcon) c; + desktopIcon.setLayout(new BorderLayout()); + frame = desktopIcon.getInternalFrame(); + + installDefaults(); + installComponents(); + installListeners(); + + desktopIcon.setOpaque(true); + } + } + + /** + * This method uninstalls the UI for the given JComponent. + * + * @param c The JComponent to uninstall this UI for. + */ + public void uninstallUI(JComponent c) + { + desktopIcon.setOpaque(false); + + uninstallListeners(); + uninstallComponents(); + uninstallDefaults(); + + frame = null; + desktopIcon.setLayout(null); + desktopIcon = null; + } + + /** + * This method installs the necessary sub components for the JDesktopIcon. + */ + protected void installComponents() + { + // Try to create a button based on what the frame's + // state is currently + button = new BoundButton(frame.getTitle()); + button.setHorizontalAlignment(SwingConstants.LEFT); + button.setHorizontalTextPosition(SwingConstants.TRAILING); + + Icon use = frame.getFrameIcon(); + if (use == null) + use = defaultIcon; + button.setIcon(use); + + desktopIcon.add(button, SwingConstants.CENTER); + } + + /** + * This method uninstalls the sub components for the JDesktopIcon. + */ + protected void uninstallComponents() + { + desktopIcon.remove(button); + + button = null; + } + + /** + * This method installs the listeners needed by this UI. + */ + protected void installListeners() + { + mouseHandler = createMouseInputListener(); + + desktopIcon.addMouseMotionListener(mouseHandler); + desktopIcon.addMouseListener(mouseHandler); + + propertyHandler = new PropertyChangeListener() + { + public void propertyChange(PropertyChangeEvent e) + { + if (e.getPropertyName().equals(JInternalFrame.TITLE_PROPERTY)) + button.setText(desktopIcon.getInternalFrame().getTitle()); + else if (e.getPropertyName().equals(JInternalFrame.FRAME_ICON_PROPERTY)) + { + Icon use = desktopIcon.getInternalFrame().getFrameIcon(); + if (use == null) + use = defaultIcon; + button.setIcon(use); + } + desktopIcon.revalidate(); + desktopIcon.repaint(); + } + }; + frame.addPropertyChangeListener(propertyHandler); + + button.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent e) + { + deiconize(); + } + }); + } + + /** + * This method uninstalls the listeners needed by the UI. + */ + protected void uninstallListeners() + { + // button is nulled so no need to remove it. + + frame.removePropertyChangeListener(propertyHandler); + propertyHandler = null; + + desktopIcon.removeMouseMotionListener(mouseHandler); + desktopIcon.removeMouseListener(mouseHandler); + } + + /** + * This method installs the defaults for the JDesktopIcon. + */ + protected void installDefaults() + { + // FIXME: Move border to defaults. + desktopIcon.setBorder(new DesktopIconBorder()); + } + + /** + * This method uninstalls the defaults for the JDesktopIcon. + */ + protected void uninstallDefaults() + { + desktopIcon.setBorder(null); + } + + /** + * This method creates a new MouseInputListener for the JDesktopIcon. + * + * @return A new MouseInputListener. + */ + protected MouseInputListener createMouseInputListener() + { + return new MouseInputHandler(); + } + + /** + * This method returns the preferred size for the given JComponent. + * + * @param c The JComponent to find a preferred size for. + * + * @return The preferred size. + */ + public Dimension getPreferredSize(JComponent c) + { + return new Dimension(iconWidth, iconHeight); + } + + /** + * This method returns the minimum size for the given JComponent. + * + * @param c The JComponent to find a minimum size for. + * + * @return The minimum size. + */ + public Dimension getMinimumSize(JComponent c) + { + return getPreferredSize(c); + } + + /** + * This method returns the maximum size for the given JComponent. + * + * @param c The JComponent to find a maximum size for. + * + * @return The maximum size. + */ + public Dimension getMaximumSize(JComponent c) + { + return getPreferredSize(c); + } + + /** + * This method returns the insets of the given JComponent. + * + * @param c The JComponent to find insets for. + * + * @return The insets of the given JComponent. + */ + public Insets getInsets(JComponent c) + { + return c.getInsets(); + } + + /** + * This method deiconizes the JInternalFrame associated with the JDesktopIcon. + */ + public void deiconize() + { + try + { + frame.setIcon(false); + } + catch (PropertyVetoException pve) + { + // We do nothing if the attempt has been vetoed. + } + } +} diff --git a/libjava/classpath/javax/swing/plaf/basic/BasicDesktopPaneUI.java b/libjava/classpath/javax/swing/plaf/basic/BasicDesktopPaneUI.java new file mode 100644 index 000000000..cbc3f9f89 --- /dev/null +++ b/libjava/classpath/javax/swing/plaf/basic/BasicDesktopPaneUI.java @@ -0,0 +1,470 @@ +/* BasicDesktopPaneUI.java -- + Copyright (C) 2004 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 java.awt.Color; +import java.awt.Dimension; +import java.awt.event.ActionEvent; +import java.beans.PropertyVetoException; + +import javax.swing.AbstractAction; +import javax.swing.DefaultDesktopManager; +import javax.swing.DesktopManager; +import javax.swing.JComponent; +import javax.swing.JDesktopPane; +import javax.swing.JInternalFrame; +import javax.swing.KeyStroke; +import javax.swing.UIManager; +import javax.swing.plaf.ComponentUI; +import javax.swing.plaf.DesktopPaneUI; +import javax.swing.plaf.UIResource; + +/** + * This class is the UI delegate for JDesktopPane for the Basic look and feel. + */ +public class BasicDesktopPaneUI extends DesktopPaneUI +{ + /** + * This helper class is used to handle key events that cause JInternalFrames + * to be closed. + */ + protected class CloseAction extends AbstractAction + { + /** + * This method is called when the action is performed. + * + * @param e The ActionEvent. + */ + public void actionPerformed(ActionEvent e) + { + if (desktop.getSelectedFrame() != null) + { + try + { + desktop.getSelectedFrame().setClosed(true); + } + catch (PropertyVetoException pve) + { + // We do nothing if the attempts has been vetoed. + } + } + } + + /** + * This method returns whether the action is enabled. + * + * @return Whether the action is enabled. + */ + public boolean isEnabled() + { + if (desktop.getSelectedFrame() != null) + return desktop.getSelectedFrame().isClosable(); + return false; + } + } + + /** + * This helper class is used to handle key events that cause JInternalFrames + * to be maximized. + */ + protected class MaximizeAction extends AbstractAction + { + /** + * This method is called when the action is performed. + * + * @param e The ActionEvent. + */ + public void actionPerformed(ActionEvent e) + { + if (desktop.getSelectedFrame() != null) + { + try + { + desktop.getSelectedFrame().setMaximum(true); + } + catch (PropertyVetoException pve) + { + // We do nothing if the attempts has been vetoed. + } + } + } + + /** + * This method returns whether the action is enabled. + * + * @return Whether the action is enabled. + */ + public boolean isEnabled() + { + if (desktop.getSelectedFrame() != null) + return desktop.getSelectedFrame().isMaximizable(); + return false; + } + } + + /** + * This helper class is used to handle key events that cause JInternalFrames + * to be minimized. + */ + protected class MinimizeAction extends AbstractAction + { + /** + * This method is called when the action is performed. + * + * @param e The ActionEvent. + */ + public void actionPerformed(ActionEvent e) + { + if (desktop.getSelectedFrame() != null) + { + try + { + desktop.getSelectedFrame().setIcon(true); + } + catch (PropertyVetoException pve) + { + // We do nothing if the attempt has been vetoed. + } + } + } + + /** + * This method returns whether the action is enabled. + * + * @return Whether the action is enabled. + */ + public boolean isEnabled() + { + if (desktop.getSelectedFrame() != null) + return desktop.getSelectedFrame().isIconifiable(); + return false; + } + } + + /** + * This helper class is used to handle key events that pass the SELECTED + * property to the next JInternalFrame in the JDesktopPane's list of + * children. + */ + protected class NavigateAction extends AbstractAction + { + /** + * This method is called when the action is performed. + * + * @param e The ActionEvent. + */ + public void actionPerformed(ActionEvent e) + { + // This is supposed to set the next selected frame. + JInternalFrame[] frames = desktop.getAllFrames(); + if (frames.length == 0) + return; + + JInternalFrame sFrame = frames[0]; + if (desktop.getSelectedFrame() != null) + sFrame = desktop.getSelectedFrame(); + + int i = 0; + for (; i < frames.length; i++) + if (frames[i] == sFrame) + break; + + // FIXME: Navigate actions go reverse too. + if (i == frames.length) + i = 0; + + desktop.setSelectedFrame(frames[i]); + } + + /** + * This method returns whether the action is enabled. + * + * @return Whether this action is enabled. + */ + public boolean isEnabled() + { + // Always true. + return true; + } + } + + /** + * This helper class is used to restore the JInternalFrame to its original + * size before maximizing or iconifying. + */ + protected class OpenAction extends AbstractAction + { + /** + * This method is called when the action is performed. + * + * @param e The ActionEvent. + */ + public void actionPerformed(ActionEvent e) + { + JInternalFrame frame = desktop.getSelectedFrame(); + if (frame != null) + { + try + { + if (frame.isIcon()) + frame.setIcon(false); + else if (frame.isMaximum()) + frame.setMaximum(false); + } + catch (PropertyVetoException pve) + { + // We do nothing if the attempt has been vetoed. + } + } + } + + /** + * This method returns whether the action is enabled. + * + * @return Whether this action is enabled. + */ + public boolean isEnabled() + { + // JInternalFrames are always restorable. + return true; + } + } + + /** + * The KeyStroke associated with closing JInternalFrames. + * @deprecated + */ + protected KeyStroke closeKey; + + /** + * The KeyStroke associated with maximizing JInternalFrames. + * @deprecated + */ + protected KeyStroke maximizeKey; + + /** + * The KeyStroke associated with minimizing JInternalFrames. + * @deprecated + */ + protected KeyStroke minimizeKey; + + /** + * The KeyStroke associated with navigating (forward?) through + * JInternalFrames. + * @deprecated + */ + protected KeyStroke navigateKey; + + /** + * The KeyStroke associated with navigating (backward?) through + * JInternalFrames. + * @deprecated + */ + protected KeyStroke navigateKey2; + + /** The default desktop manager used with JDesktopPane. */ + protected DesktopManager desktopManager; + + /** The JDesktopPane this UI is used with. */ + protected JDesktopPane desktop; + + /** + * Creates a new BasicDesktopPaneUI object. + */ + public BasicDesktopPaneUI() + { + // Nothing to do here. + } + + /** + * This method creates a BasicDesktopPaneUI for the given JComponent. + * + * @param c The JComponent to create a UI for. + * + * @return A new BasicDesktopPaneUI. + */ + public static ComponentUI createUI(JComponent c) + { + return new BasicDesktopPaneUI(); + } + + /** + * This method returns the maximum size for the given JComponent. + * + * @param c The JComponent to find a maximum size for. + * + * @return The maximum size for the given JComponent. + */ + public Dimension getMaximumSize(JComponent c) + { + return getPreferredSize(c); + } + + /** + * This method returns the minimum size for the given JComponent. + * + * @param c The JComponent to find a minimum size for. + * + * @return The minimum size for the given JComponent. + */ + public Dimension getMinimumSize(JComponent c) + { + return getPreferredSize(c); + } + + /** + * This method returns the preferred size for the given JComponent. + * + * @param c The JComponent to find a preferred size for. + * + * @return The preferred size for the given JComponent. + */ + public Dimension getPreferredSize(JComponent c) + { + // return null because JDesktopPanes don't have preferred sizes. + return null; + } + + /** + * This method installs the defaults for the JDesktopPane provided by the + * current look and feel. + */ + protected void installDefaults() + { + Color bg = desktop.getBackground(); + if (bg == null || bg instanceof UIResource) + desktop.setBackground(UIManager.getColor("desktop")); + } + + /** + * This method installs the desktop manager for the JDesktopPane. + */ + protected void installDesktopManager() + { + desktopManager = new DefaultDesktopManager(); + desktop.setDesktopManager(desktopManager); + } + + /** + * This method installs the keyboard actions for the JDesktopPane. + */ + protected void installKeyboardActions() + { + // FIXME: create actions and keystrokes. + registerKeyboardActions(); + } + + /** + * This method installs the UI for the given JComponent. + * + * @param c The JComponent to install this UI for. + */ + public void installUI(JComponent c) + { + if (c instanceof JDesktopPane) + { + desktop = (JDesktopPane) c; + + installDefaults(); + installDesktopManager(); + installKeyboardActions(); + } + } + + /** + * This method registers the actions to the appropriate Action and Input + * maps. + */ + protected void registerKeyboardActions() + { + // FIXME: Do the binding. + // XXX: the gtk windows tend to intercept a lot of the + // key events for themselves. must figure a way past that + // before binding + } + + /** + * This method reverses the work done by the installDefaults method. + */ + protected void uninstallDefaults() + { + desktop.setBackground(null); + } + + /** + * This method reverses the work done by the installDesktopManager method. + */ + protected void uninstallDesktopManager() + { + desktopManager = null; + desktop.setDesktopManager(null); + } + + /** + * This method reverses the work done by the installKeyboardActions method. + */ + protected void uninstallKeyboardActions() + { + unregisterKeyboardActions(); + // FIXME: null the actions and keystrokes. + } + + /** + * This method reverses the work done by the registerKeyboardActions method. + */ + protected void unregisterKeyboardActions() + { + // FIXME: unmap the keystrokes + } + + /** + * This method uninstalls the UI for the given JComponent. It should reverse + * all the work done by the installUI method. + * + * @param c The JComponent to uninstall this UI for. + */ + public void uninstallUI(JComponent c) + { + uninstallKeyboardActions(); + uninstallDesktopManager(); + uninstallDefaults(); + + desktop = null; + } +} diff --git a/libjava/classpath/javax/swing/plaf/basic/BasicDirectoryModel.java b/libjava/classpath/javax/swing/plaf/basic/BasicDirectoryModel.java new file mode 100644 index 000000000..62657ad86 --- /dev/null +++ b/libjava/classpath/javax/swing/plaf/basic/BasicDirectoryModel.java @@ -0,0 +1,586 @@ +/* BasicDirectoryModel.java -- + Copyright (C) 2005, 2006 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 java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.io.File; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.Vector; +import javax.swing.AbstractListModel; +import javax.swing.JFileChooser; +import javax.swing.SwingUtilities; +import javax.swing.event.ListDataEvent; +import javax.swing.filechooser.FileSystemView; + + +/** + * Implements an AbstractListModel for directories where the source + * of the files is a JFileChooser object. + * + * This class is used for sorting and ordering the file list in + * a JFileChooser L&F object. + */ +public class BasicDirectoryModel extends AbstractListModel + implements PropertyChangeListener +{ + /** The list of files itself */ + private Vector contents; + + /** + * The directories in the list. + */ + private Vector directories; + + /** + * The files in the list. + */ + private Vector files; + + /** The listing mode of the associated JFileChooser, + either FILES_ONLY, DIRECTORIES_ONLY or FILES_AND_DIRECTORIES */ + private int listingMode; + + /** The JFileCooser associated with this model */ + private JFileChooser filechooser; + + /** + * The thread that loads the file view. + */ + private DirectoryLoadThread loadThread; + + /** + * This thread is responsible for loading file lists from the + * current directory and updating the model. + */ + private class DirectoryLoadThread extends Thread + { + + /** + * Updates the Swing list model. + */ + private class UpdateSwingRequest + implements Runnable + { + + private List added; + private int addIndex; + private List removed; + private int removeIndex; + private boolean cancel; + + UpdateSwingRequest(List add, int ai, List rem, int ri) + { + added = add; + addIndex = ai; + removed = rem; + removeIndex = ri; + cancel = false; + } + + public void run() + { + if (! cancel) + { + int numRemoved = removed == null ? 0 : removed.size(); + int numAdded = added == null ? 0 : added.size(); + synchronized (contents) + { + if (numRemoved > 0) + contents.removeAll(removed); + if (numAdded > 0) + contents.addAll(added); + + files = null; + directories = null; + } + if (numRemoved > 0 && numAdded == 0) + fireIntervalRemoved(BasicDirectoryModel.this, removeIndex, + removeIndex + numRemoved - 1); + else if (numRemoved == 0 && numAdded > 0) + fireIntervalAdded(BasicDirectoryModel.this, addIndex, + addIndex + numAdded - 1); + else + fireContentsChanged(); + } + } + + void cancel() + { + cancel = true; + } + } + + /** + * The directory beeing loaded. + */ + File directory; + + /** + * Stores all UpdateSwingRequests that are sent to the event queue. + */ + private UpdateSwingRequest pending; + + /** + * Creates a new DirectoryLoadThread that loads the specified + * directory. + * + * @param dir the directory to load + */ + DirectoryLoadThread(File dir) + { + super("Basic L&F directory loader"); + directory = dir; + } + + public void run() + { + FileSystemView fsv = filechooser.getFileSystemView(); + File[] files = fsv.getFiles(directory, + filechooser.isFileHidingEnabled()); + + // Occasional check if we have been interrupted. + if (isInterrupted()) + return; + + // Check list for accepted files. + Vector accepted = new Vector(); + for (int i = 0; i < files.length; i++) + { + if (filechooser.accept(files[i])) + accepted.add(files[i]); + } + + // Occasional check if we have been interrupted. + if (isInterrupted()) + return; + + // Sort list. + sort(accepted); + + // Now split up directories from files so that we get the directories + // listed before the files. + Vector newFiles = new Vector(); + Vector newDirectories = new Vector(); + for (Iterator i = accepted.iterator(); i.hasNext();) + { + File f = (File) i.next(); + boolean traversable = filechooser.isTraversable(f); + if (traversable) + newDirectories.add(f); + else if (! traversable && filechooser.isFileSelectionEnabled()) + newFiles.add(f); + + // Occasional check if we have been interrupted. + if (isInterrupted()) + return; + + } + + // Build up new file cache. Try to update only the changed elements. + // This will be important for actions like adding new files or + // directories inside a large file list. + Vector newCache = new Vector(newDirectories); + newCache.addAll(newFiles); + + int newSize = newCache.size(); + int oldSize = contents.size(); + if (newSize < oldSize) + { + // Check for removed interval. + int start = -1; + int end = -1; + boolean found = false; + for (int i = 0; i < newSize && !found; i++) + { + if (! newCache.get(i).equals(contents.get(i))) + { + start = i; + end = i + oldSize - newSize; + found = true; + } + } + if (start >= 0 && end > start + && contents.subList(end, oldSize) + .equals(newCache.subList(start, newSize))) + { + // Occasional check if we have been interrupted. + if (isInterrupted()) + return; + + Vector removed = new Vector(contents.subList(start, end)); + UpdateSwingRequest r = new UpdateSwingRequest(null, 0, + removed, start); + invokeLater(r); + newCache = null; + } + } + else if (newSize > oldSize) + { + // Check for inserted interval. + int start = oldSize; + int end = newSize; + boolean found = false; + for (int i = 0; i < oldSize && ! found; i++) + { + if (! newCache.get(i).equals(contents.get(i))) + { + start = i; + boolean foundEnd = false; + for (int j = i; j < newSize && ! foundEnd; j++) + { + if (newCache.get(j).equals(contents.get(i))) + { + end = j; + foundEnd = true; + } + } + end = i + oldSize - newSize; + } + } + if (start >= 0 && end > start + && newCache.subList(end, newSize) + .equals(contents.subList(start, oldSize))) + { + // Occasional check if we have been interrupted. + if (isInterrupted()) + return; + + List added = newCache.subList(start, end); + UpdateSwingRequest r = new UpdateSwingRequest(added, start, + null, 0); + invokeLater(r); + newCache = null; + } + } + + // Handle complete list changes (newCache != null). + if (newCache != null && ! contents.equals(newCache)) + { + // Occasional check if we have been interrupted. + if (isInterrupted()) + return; + UpdateSwingRequest r = new UpdateSwingRequest(newCache, 0, + contents, 0); + invokeLater(r); + } + } + + /** + * Wraps SwingUtilities.invokeLater() and stores the request in + * a Vector so that we can still cancel it later. + * + * @param update the request to invoke + */ + private void invokeLater(UpdateSwingRequest update) + { + pending = update; + SwingUtilities.invokeLater(update); + } + + /** + * Cancels all pending update requests that might be in the AWT + * event queue. + */ + void cancelPending() + { + if (pending != null) + pending.cancel(); + } + } + + /** A Comparator class/object for sorting the file list. */ + private Comparator comparator = new Comparator() + { + public int compare(Object o1, Object o2) + { + if (lt((File) o1, (File) o2)) + return -1; + else + return 1; + } + }; + + /** + * Creates a new BasicDirectoryModel object. + * + * @param filechooser DOCUMENT ME! + */ + public BasicDirectoryModel(JFileChooser filechooser) + { + this.filechooser = filechooser; + filechooser.addPropertyChangeListener(this); + listingMode = filechooser.getFileSelectionMode(); + contents = new Vector(); + validateFileCache(); + } + + /** + * Returns whether a given (File) object is included in the list. + * + * @param o - The file object to test. + * + * @return <code>true</code> if the list contains the given object. + */ + public boolean contains(Object o) + { + return contents.contains(o); + } + + /** + * Fires a content change event. + */ + public void fireContentsChanged() + { + fireContentsChanged(this, 0, getSize() - 1); + } + + /** + * Returns a Vector of (java.io.File) objects containing + * the directories in this list. + * + * @return a Vector + */ + public Vector<File> getDirectories() + { + // Synchronize this with the UpdateSwingRequest for the case when + // contents is modified. + synchronized (contents) + { + Vector dirs = directories; + if (dirs == null) + { + // Initializes this in getFiles(). + getFiles(); + dirs = directories; + } + return dirs; + } + } + + /** + * Returns the (java.io.File) object at + * an index in the list. + * + * @param index The list index + * @return a File object + */ + public Object getElementAt(int index) + { + if (index > getSize() - 1) + return null; + return contents.elementAt(index); + } + + /** + * Returns a Vector of (java.io.File) objects containing + * the files in this list. + * + * @return a Vector + */ + public Vector<File> getFiles() + { + synchronized (contents) + { + Vector f = files; + if (f == null) + { + f = new Vector(); + Vector d = new Vector(); // Directories; + for (Iterator i = contents.iterator(); i.hasNext();) + { + File file = (File) i.next(); + if (filechooser.isTraversable(file)) + d.add(file); + else + f.add(file); + } + files = f; + directories = d; + } + return f; + } + } + + /** + * Returns the size of the list, which only includes directories + * if the JFileChooser is set to DIRECTORIES_ONLY. + * + * Otherwise, both directories and files are included in the count. + * + * @return The size of the list. + */ + public int getSize() + { + return contents.size(); + } + + /** + * Returns the index of an (java.io.File) object in the list. + * + * @param o The object - normally a File. + * + * @return the index of that object, or -1 if it is not in the list. + */ + public int indexOf(Object o) + { + return contents.indexOf(o); + } + + /** + * Obsoleted method which does nothing. + */ + public void intervalAdded(ListDataEvent e) + { + // obsoleted + } + + /** + * Obsoleted method which does nothing. + */ + public void intervalRemoved(ListDataEvent e) + { + // obsoleted + } + + /** + * Obsoleted method which does nothing. + */ + public void invalidateFileCache() + { + // obsoleted + } + + /** + * Less than, determine the relative order in the list of two files + * for sorting purposes. + * + * The order is: directories < files, and thereafter alphabetically, + * using the default locale collation. + * + * @param a the first file + * @param b the second file + * + * @return <code>true</code> if a > b, <code>false</code> if a < b. + */ + protected boolean lt(File a, File b) + { + boolean aTrav = filechooser.isTraversable(a); + boolean bTrav = filechooser.isTraversable(b); + + if (aTrav == bTrav) + { + String aname = a.getName().toLowerCase(); + String bname = b.getName().toLowerCase(); + return (aname.compareTo(bname) < 0) ? true : false; + } + else + { + if (aTrav) + return true; + else + return false; + } + } + + /** + * Listens for a property change; the change in file selection mode of the + * associated JFileChooser. Reloads the file cache on that event. + * + * @param e - A PropertyChangeEvent. + */ + public void propertyChange(PropertyChangeEvent e) + { + String property = e.getPropertyName(); + if (property.equals(JFileChooser.DIRECTORY_CHANGED_PROPERTY) + || property.equals(JFileChooser.FILE_FILTER_CHANGED_PROPERTY) + || property.equals(JFileChooser.FILE_HIDING_CHANGED_PROPERTY) + || property.equals(JFileChooser.FILE_SELECTION_MODE_CHANGED_PROPERTY) + || property.equals(JFileChooser.FILE_VIEW_CHANGED_PROPERTY) + ) + { + validateFileCache(); + } + } + + /** + * Renames a file - However, does <I>not</I> re-sort the list + * or replace the old file with the new one in the list. + * + * @param oldFile The old file + * @param newFile The new file name + * + * @return <code>true</code> if the rename succeeded + */ + public boolean renameFile(File oldFile, File newFile) + { + return oldFile.renameTo( newFile ); + } + + /** + * Sorts a Vector of File objects. + * + * @param v The Vector to sort. + */ + protected void sort(Vector<? extends File> v) + { + Collections.sort(v, comparator); + } + + /** + * Re-loads the list of files + */ + public void validateFileCache() + { + File dir = filechooser.getCurrentDirectory(); + if (dir != null) + { + // Cancel all pending requests. + if (loadThread != null) + { + loadThread.interrupt(); + loadThread.cancelPending(); + } + loadThread = new DirectoryLoadThread(dir); + loadThread.start(); + } + } +} diff --git a/libjava/classpath/javax/swing/plaf/basic/BasicEditorPaneUI.java b/libjava/classpath/javax/swing/plaf/basic/BasicEditorPaneUI.java new file mode 100644 index 000000000..6ddd2513b --- /dev/null +++ b/libjava/classpath/javax/swing/plaf/basic/BasicEditorPaneUI.java @@ -0,0 +1,96 @@ +/* BasicEditorPaneUI.java -- + Copyright (C) 2004 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 javax.swing.JComponent; +import javax.swing.JEditorPane; +import javax.swing.plaf.ComponentUI; +import javax.swing.text.EditorKit; +import javax.swing.text.JTextComponent; + +/** + * The UI class for {@link JEditorPane}s. + * + * @author original author unknown + * @author Roman Kennke (roman@kennke.org) + */ +public class BasicEditorPaneUI extends BasicTextUI +{ + /** + * Creates an instance of <code>BasicEditorPaneUI</code> for the text + * component <code>comp</code>. + * + * @param comp the component for which to create an UI + * + * @return the UI for <code>comp</code> + */ + public static ComponentUI createUI(JComponent comp) + { + return new BasicEditorPaneUI(); + } + + /** + * Creates a new <code>BasicEditorPaneUI</code> + */ + public BasicEditorPaneUI() + { + // Do nothing here. + } + + /** + * Returns the property prefix to be used by this UI class. This is + * <code>EditorPane</code> in this case. + * + * @return <code>EditorPane</code> + */ + protected String getPropertyPrefix() + { + return "EditorPane"; + } + + /** + * Gets the EditorKit for the text component. + * + * @param textComponent the text component for which to fetch the editor kit + */ + public EditorKit getEditorKit(JTextComponent textComponent) + { + return ((JEditorPane) textComponent).getEditorKit(); + } +} diff --git a/libjava/classpath/javax/swing/plaf/basic/BasicFileChooserUI.java b/libjava/classpath/javax/swing/plaf/basic/BasicFileChooserUI.java new file mode 100644 index 000000000..347686d6e --- /dev/null +++ b/libjava/classpath/javax/swing/plaf/basic/BasicFileChooserUI.java @@ -0,0 +1,1437 @@ +/* BasicFileChooserUI.java -- + Copyright (C) 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 java.awt.Window; +import java.awt.event.ActionEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.beans.PropertyChangeListener; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Hashtable; + +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.Icon; +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JDialog; +import javax.swing.JFileChooser; +import javax.swing.JList; +import javax.swing.JPanel; +import javax.swing.JTextField; +import javax.swing.SwingUtilities; +import javax.swing.UIDefaults; +import javax.swing.UIManager; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; +import javax.swing.filechooser.FileFilter; +import javax.swing.filechooser.FileSystemView; +import javax.swing.filechooser.FileView; +import javax.swing.plaf.ComponentUI; +import javax.swing.plaf.FileChooserUI; +import javax.swing.plaf.metal.MetalIconFactory; + + +/** + * A UI delegate for the {@link JFileChooser} component under the + * {@link BasicLookAndFeel}. + */ +public class BasicFileChooserUI extends FileChooserUI +{ + /** + * A file filter that accepts all files. + */ + protected class AcceptAllFileFilter extends FileFilter + { + /** + * Creates a new instance. + */ + public AcceptAllFileFilter() + { + // Nothing to do here. + } + + /** + * Returns <code>true</code> always, as all files are accepted by this + * filter. + * + * @param f the file. + * + * @return Always <code>true</code>. + */ + public boolean accept(File f) + { + return true; + } + + /** + * Returns a description for this filter. + * + * @return A description for the file filter. + */ + public String getDescription() + { + return acceptAllFileFilterText; + } + } + + /** + * Handles a user action to approve the dialog selection. + * + * @see BasicFileChooserUI#getApproveSelectionAction() + */ + protected class ApproveSelectionAction extends AbstractAction + { + /** + * Creates a new ApproveSelectionAction object. + */ + protected ApproveSelectionAction() + { + super("approveSelection"); + } + + /** + * Sets the current selection and closes the dialog. + * + * @param e the action event. + */ + public void actionPerformed(ActionEvent e) + { + Object obj = null; + if (parentPath != null) + obj = new String(parentPath + getFileName()); + else + obj = filechooser.getSelectedFile(); + if (obj != null) + { + File f = filechooser.getFileSystemView().createFileObject(obj.toString()); + File currSelected = filechooser.getSelectedFile(); + if (filechooser.isTraversable(f)) + { + filechooser.setCurrentDirectory(currSelected); + filechooser.rescanCurrentDirectory(); + } + else + { + filechooser.approveSelection(); + closeDialog(); + } + } + else + { + File f = new File(filechooser.getCurrentDirectory(), getFileName()); + if ( selectedDir != null ) + f = selectedDir; + if (filechooser.isTraversable(f)) + { + filechooser.setCurrentDirectory(f); + filechooser.rescanCurrentDirectory(); + } + else + { + filechooser.setSelectedFile(f); + filechooser.approveSelection(); + closeDialog(); + } + } + } + } + + /** + * Provides presentation information about files and directories. + */ + protected class BasicFileView extends FileView + { + /** Storage for cached icons. */ + protected Hashtable<File, Icon> iconCache = new Hashtable<File, Icon>(); + + /** + * Creates a new instance. + */ + public BasicFileView() + { + // Nothing to do here. + } + + /** + * Adds an icon to the cache, associating it with the given file/directory. + * + * @param f the file/directory. + * @param i the icon. + */ + public void cacheIcon(File f, Icon i) + { + iconCache.put(f, i); + } + + /** + * Clears the icon cache. + */ + public void clearIconCache() + { + iconCache.clear(); + } + + /** + * Retrieves the icon associated with the specified file/directory, if + * there is one. + * + * @param f the file/directory. + * + * @return The cached icon (or <code>null</code>). + */ + public Icon getCachedIcon(File f) + { + return (Icon) iconCache.get(f); + } + + /** + * Returns a description of the given file/directory. In this + * implementation, the description is the same as the name returned by + * {@link #getName(File)}. + * + * @param f the file/directory. + * + * @return A description of the given file/directory. + */ + public String getDescription(File f) + { + return getName(f); + } + + /** + * Returns an icon appropriate for the given file or directory. + * + * @param f the file/directory. + * + * @return An icon. + */ + public Icon getIcon(File f) + { + Icon val = getCachedIcon(f); + if (val != null) + return val; + if (filechooser.isTraversable(f)) + val = directoryIcon; + else + val = fileIcon; + cacheIcon(f, val); + return val; + } + + /** + * Returns the name for the given file/directory. + * + * @param f the file/directory. + * + * @return The name of the file/directory. + */ + public String getName(File f) + { + String name = null; + if (f != null) + { + JFileChooser c = getFileChooser(); + FileSystemView v = c.getFileSystemView(); + name = v.getSystemDisplayName(f); + } + return name; + } + + /** + * Returns a localised description for the type of file/directory. + * + * @param f the file/directory. + * + * @return A type description for the given file/directory. + */ + public String getTypeDescription(File f) + { + if (filechooser.isTraversable(f)) + return dirDescText; + else + return fileDescText; + } + + /** + * Returns {@link Boolean#TRUE} if the given file/directory is hidden, + * and {@link Boolean#FALSE} otherwise. + * + * @param f the file/directory. + * + * @return {@link Boolean#TRUE} or {@link Boolean#FALSE}. + */ + public Boolean isHidden(File f) + { + return Boolean.valueOf(filechooser.getFileSystemView().isHiddenFile(f)); + } + } + + /** + * Handles an action to cancel the file chooser. + * + * @see BasicFileChooserUI#getCancelSelectionAction() + */ + protected class CancelSelectionAction extends AbstractAction + { + /** + * Creates a new <code>CancelSelectionAction</code> object. + */ + protected CancelSelectionAction() + { + super(null); + } + + /** + * Cancels the selection and closes the dialog. + * + * @param e the action event (ignored). + */ + public void actionPerformed(ActionEvent e) + { + filechooser.setSelectedFile(null); + filechooser.setSelectedFiles(null); + filechooser.cancelSelection(); + closeDialog(); + } + } + + /** + * An action to handle changes to the parent directory (for example, via + * a click on the "up folder" button). + * + * @see BasicFileChooserUI#getChangeToParentDirectoryAction() + */ + protected class ChangeToParentDirectoryAction extends AbstractAction + { + /** + * Creates a new <code>ChangeToParentDirectoryAction</code> object. + */ + protected ChangeToParentDirectoryAction() + { + super("Go Up"); + } + + /** + * Handles the action event. + * + * @param e the action event. + */ + public void actionPerformed(ActionEvent e) + { + filechooser.changeToParentDirectory(); + filechooser.revalidate(); + filechooser.repaint(); + } + } + + /** + * A mouse listener that handles double-click events. + * + * @see BasicFileChooserUI#createDoubleClickListener(JFileChooser, JList) + */ + protected class DoubleClickListener extends MouseAdapter + { + + /** DOCUMENT ME! */ + private Object lastSelected; + + /** DOCUMENT ME! */ + private JList list; + + /** + * Creates a new DoubleClickListener object. + * + * @param list DOCUMENT ME! + */ + public DoubleClickListener(JList list) + { + this.list = list; + lastSelected = list.getSelectedValue(); + setDirectorySelected(false); + } + + /** + * Handles a mouse click event. + * + * @param e the event. + */ + public void mouseClicked(MouseEvent e) + { + Object p = list.getSelectedValue(); + if (p == null) + return; + FileSystemView fsv = filechooser.getFileSystemView(); + if (e.getClickCount() >= 2 && lastSelected != null && + p.toString().equals(lastSelected.toString())) + { + File f = fsv.createFileObject(lastSelected.toString()); + if (filechooser.isTraversable(f)) + { + filechooser.setCurrentDirectory(f); + filechooser.rescanCurrentDirectory(); + } + else + { + filechooser.setSelectedFile(f); + filechooser.approveSelection(); + closeDialog(); + } + } + else // single click + { + String path = p.toString(); + File f = fsv.createFileObject(path); + filechooser.setSelectedFile(f); + + if (filechooser.isMultiSelectionEnabled()) + { + int[] inds = list.getSelectedIndices(); + File[] allFiles = new File[inds.length]; + for (int i = 0; i < inds.length; i++) + allFiles[i] = (File) list.getModel().getElementAt(inds[i]); + filechooser.setSelectedFiles(allFiles); + } + + if (filechooser.isTraversable(f)) + { + setDirectorySelected(true); + setDirectory(f); + } + else + { + setDirectorySelected(false); + setDirectory(null); + } + lastSelected = path; + parentPath = f.getParent(); + + if (f.isFile()) + setFileName(f.getName()); + else if (filechooser.getFileSelectionMode() != + JFileChooser.FILES_ONLY) + setFileName(path); + } + } + + /** + * Handles a mouse entered event (NOT IMPLEMENTED). + * + * @param e the mouse event. + */ + public void mouseEntered(MouseEvent e) + { + // FIXME: Implement + } + } + + /** + * An action that changes the file chooser to display the user's home + * directory. + * + * @see BasicFileChooserUI#getGoHomeAction() + */ + protected class GoHomeAction extends AbstractAction + { + /** + * Creates a new <code>GoHomeAction</code> object. + */ + protected GoHomeAction() + { + super("Go Home"); + } + + /** + * Sets the directory to the user's home directory, and repaints the + * file chooser component. + * + * @param e the action event (ignored). + */ + public void actionPerformed(ActionEvent e) + { + filechooser.setCurrentDirectory(filechooser.getFileSystemView() + .getHomeDirectory()); + filechooser.revalidate(); + filechooser.repaint(); + } + } + + /** + * An action that handles the creation of a new folder/directory. + * + * @see BasicFileChooserUI#getNewFolderAction() + */ + protected class NewFolderAction extends AbstractAction + { + /** + * Creates a new <code>NewFolderAction</code> object. + */ + protected NewFolderAction() + { + super("New Folder"); + } + + /** + * Handles the event by creating a new folder. + * + * @param e the action event (ignored). + */ + public void actionPerformed(ActionEvent e) + { + try + { + filechooser.getFileSystemView().createNewFolder(filechooser + .getCurrentDirectory()); + } + catch (IOException ioe) + { + return; + } + filechooser.rescanCurrentDirectory(); + filechooser.repaint(); + } + } + + /** + * A listener for selection events in the file list. + * + * @see BasicFileChooserUI#createListSelectionListener(JFileChooser) + */ + protected class SelectionListener implements ListSelectionListener + { + /** + * Creates a new <code>SelectionListener</code> object. + */ + protected SelectionListener() + { + // Nothing to do here. + } + + /** + * Sets the JFileChooser to the selected file on an update + * + * @param e DOCUMENT ME! + */ + public void valueChanged(ListSelectionEvent e) + { + JList list = (JList) e.getSource(); + Object f = list.getSelectedValue(); + if (f == null) + return; + File file = filechooser.getFileSystemView().createFileObject(f.toString()); + if (! filechooser.isTraversable(file)) + { + selectedDir = null; + filechooser.setSelectedFile(file); + } + else + { + selectedDir = file; + filechooser.setSelectedFile(null); + } + } + } + + /** + * DOCUMENT ME! + * + * @see BasicFileChooserUI#getUpdateAction() + */ + protected class UpdateAction extends AbstractAction + { + /** + * Creates a new UpdateAction object. + */ + protected UpdateAction() + { + super(null); + } + + /** + * NOT YET IMPLEMENTED. + * + * @param e the action event. + */ + public void actionPerformed(ActionEvent e) + { + // FIXME: implement this + } + } + + /** The localised mnemonic for the cancel button. */ + protected int cancelButtonMnemonic; + + /** The localised text for the cancel button. */ + protected String cancelButtonText; + + /** The localised tool tip text for the cancel button. */ + protected String cancelButtonToolTipText; + + /** An icon representing a computer. */ + protected Icon computerIcon; + + /** An icon for the "details view" button. */ + protected Icon detailsViewIcon; + + /** An icon representing a directory. */ + protected Icon directoryIcon; + + /** The localised Mnemonic for the open button. */ + protected int directoryOpenButtonMnemonic; + + /** The localised text for the open button. */ + protected String directoryOpenButtonText; + + /** The localised tool tip text for the open button. */ + protected String directoryOpenButtonToolTipText; + + /** An icon representing a file. */ + protected Icon fileIcon; + + /** An icon representing a floppy drive. */ + protected Icon floppyDriveIcon; + + /** An icon representing a hard drive. */ + protected Icon hardDriveIcon; + + /** The localised mnemonic for the "help" button. */ + protected int helpButtonMnemonic; + + /** The localised text for the "help" button. */ + protected String helpButtonText; + + /** The localised tool tip text for the help button. */ + protected String helpButtonToolTipText; + + /** An icon representing the user's home folder. */ + protected Icon homeFolderIcon; + + /** An icon for the "list view" button. */ + protected Icon listViewIcon; + + /** An icon for the "new folder" button. */ + protected Icon newFolderIcon = directoryIcon; + + /** The localised mnemonic for the "open" button. */ + protected int openButtonMnemonic; + + /** The localised text for the "open" button. */ + protected String openButtonText; + + /** The localised tool tip text for the "open" button. */ + protected String openButtonToolTipText; + + /** The localised mnemonic for the "save" button. */ + protected int saveButtonMnemonic; + + /** The localised text for the "save" button. */ + protected String saveButtonText; + + /** The localised tool tip text for the save button. */ + protected String saveButtonToolTipText; + + /** The localised mnemonic for the "update" button. */ + protected int updateButtonMnemonic; + + /** The localised text for the "update" button. */ + protected String updateButtonText; + + /** The localised tool tip text for the "update" button. */ + protected String updateButtonToolTipText; + + /** An icon for the "up folder" button. */ + protected Icon upFolderIcon; + + // -- begin private, but package local since used in inner classes -- + + /** The file chooser component represented by this UI delegate. */ + JFileChooser filechooser; + + /** The model for the directory list. */ + BasicDirectoryModel model; + + /** The file filter for all files. */ + FileFilter acceptAll = new AcceptAllFileFilter(); + + /** The default file view. */ + FileView fv = new BasicFileView(); + + /** The accept (open/save) button. */ + JButton accept; + + /** An optional accessory panel. */ + JPanel accessoryPanel = new JPanel(); + + /** A property change listener. */ + PropertyChangeListener propertyChangeListener; + + /** The text describing the filter for "all files". */ + String acceptAllFileFilterText; + + /** The text describing a directory type. */ + String dirDescText; + + /** The text describing a file type. */ + String fileDescText; + + /** Is a directory selected? */ + boolean dirSelected; + + /** The current directory. */ + File currDir; + + // FIXME: describe what is contained in the bottom panel + /** The bottom panel. */ + JPanel bottomPanel; + + /** The close panel. */ + JPanel closePanel; + + /** Text box that displays file name */ + JTextField entry; + + /** Current parent path */ + String parentPath; + + /** + * The action for the 'approve' button. + * @see #getApproveSelectionAction() + */ + private ApproveSelectionAction approveSelectionAction; + + /** + * The action for the 'cancel' button. + * @see #getCancelSelectionAction() + */ + private CancelSelectionAction cancelSelectionAction; + + /** + * The action for the 'go home' control button. + * @see #getGoHomeAction() + */ + private GoHomeAction goHomeAction; + + /** + * The action for the 'up folder' control button. + * @see #getChangeToParentDirectoryAction() + */ + private ChangeToParentDirectoryAction changeToParentDirectoryAction; + + /** + * The action for the 'new folder' control button. + * @see #getNewFolderAction() + */ + private NewFolderAction newFolderAction; + + /** + * The action for ???. // FIXME: what is this? + * @see #getUpdateAction() + */ + private UpdateAction updateAction; + + /** + * When in FILES_ONLY, mode a directory cannot be selected, so + * we save a reference to any it here. This is used to enter + * the directory on "Open" when in that mode. + */ + private File selectedDir; + + // -- end private -- + + /** + * Closes the dialog. + */ + void closeDialog() + { + Window owner = SwingUtilities.windowForComponent(filechooser); + if (owner instanceof JDialog) + ((JDialog) owner).dispose(); + } + + /** + * Creates a new <code>BasicFileChooserUI</code> object. + * + * @param b the file chooser component. + */ + public BasicFileChooserUI(JFileChooser b) + { + } + + /** + * Returns a UI delegate for the given component. + * + * @param c the component (should be a {@link JFileChooser}). + * + * @return A new UI delegate. + */ + public static ComponentUI createUI(JComponent c) + { + return new BasicFileChooserUI((JFileChooser) c); + } + + /** + * Installs the UI for the specified component. + * + * @param c the component (should be a {@link JFileChooser}). + */ + public void installUI(JComponent c) + { + if (c instanceof JFileChooser) + { + JFileChooser fc = (JFileChooser) c; + this.filechooser = fc; + fc.resetChoosableFileFilters(); + createModel(); + clearIconCache(); + installDefaults(fc); + installComponents(fc); + installListeners(fc); + + File path = filechooser.getCurrentDirectory(); + if (path != null) + parentPath = path.getParent(); + } + } + + /** + * Uninstalls this UI from the given component. + * + * @param c the component (should be a {@link JFileChooser}). + */ + public void uninstallUI(JComponent c) + { + model = null; + uninstallListeners(filechooser); + uninstallComponents(filechooser); + uninstallDefaults(filechooser); + filechooser = null; + } + + // FIXME: Indent the entries in the combobox + // Made this method package private to access it from within inner classes + // with better performance + void boxEntries() + { + ArrayList parentFiles = new ArrayList(); + File parent = filechooser.getCurrentDirectory(); + if (parent == null) + parent = filechooser.getFileSystemView().getDefaultDirectory(); + while (parent != null) + { + String name = parent.getName(); + if (name.equals("")) + name = parent.getAbsolutePath(); + + parentFiles.add(parentFiles.size(), name); + parent = parent.getParentFile(); + } + + if (parentFiles.size() == 0) + return; + + } + + /** + * Creates and install the subcomponents for the file chooser. + * + * @param fc the file chooser. + */ + public void installComponents(JFileChooser fc) + { + } + + /** + * Uninstalls the components from the file chooser. + * + * @param fc the file chooser. + */ + public void uninstallComponents(JFileChooser fc) + { + } + + /** + * Installs the listeners required by this UI delegate. + * + * @param fc the file chooser. + */ + protected void installListeners(JFileChooser fc) + { + propertyChangeListener = createPropertyChangeListener(filechooser); + if (propertyChangeListener != null) + filechooser.addPropertyChangeListener(propertyChangeListener); + fc.addPropertyChangeListener(getModel()); + } + + /** + * Uninstalls the listeners previously installed by this UI delegate. + * + * @param fc the file chooser. + */ + protected void uninstallListeners(JFileChooser fc) + { + if (propertyChangeListener != null) + { + filechooser.removePropertyChangeListener(propertyChangeListener); + propertyChangeListener = null; + } + fc.removePropertyChangeListener(getModel()); + } + + /** + * Installs the defaults for this UI delegate. + * + * @param fc the file chooser. + */ + protected void installDefaults(JFileChooser fc) + { + installIcons(fc); + installStrings(fc); + } + + /** + * Uninstalls the defaults previously added by this UI delegate. + * + * @param fc the file chooser. + */ + protected void uninstallDefaults(JFileChooser fc) + { + uninstallStrings(fc); + uninstallIcons(fc); + } + + /** + * Installs the icons for this UI delegate. + * + * @param fc the file chooser (ignored). + */ + protected void installIcons(JFileChooser fc) + { + UIDefaults defaults = UIManager.getLookAndFeelDefaults(); + computerIcon = MetalIconFactory.getTreeComputerIcon(); + detailsViewIcon = defaults.getIcon("FileChooser.detailsViewIcon"); + directoryIcon = new MetalIconFactory.TreeFolderIcon(); + fileIcon = new MetalIconFactory.TreeLeafIcon(); + floppyDriveIcon = MetalIconFactory.getTreeFloppyDriveIcon(); + hardDriveIcon = MetalIconFactory.getTreeHardDriveIcon(); + homeFolderIcon = defaults.getIcon("FileChooser.homeFolderIcon"); + listViewIcon = defaults.getIcon("FileChooser.listViewIcon"); + newFolderIcon = defaults.getIcon("FileChooser.newFolderIcon"); + upFolderIcon = defaults.getIcon("FileChooser.upFolderIcon"); + } + + /** + * Uninstalls the icons previously added by this UI delegate. + * + * @param fc the file chooser. + */ + protected void uninstallIcons(JFileChooser fc) + { + computerIcon = null; + detailsViewIcon = null; + directoryIcon = null; + fileIcon = null; + floppyDriveIcon = null; + hardDriveIcon = null; + homeFolderIcon = null; + listViewIcon = null; + newFolderIcon = null; + upFolderIcon = null; + } + + /** + * Installs the strings used by this UI delegate. + * + * @param fc the file chooser. + */ + protected void installStrings(JFileChooser fc) + { + UIDefaults defaults = UIManager.getLookAndFeelDefaults(); + + dirDescText = defaults.getString("FileChooser.directoryDescriptionText"); + fileDescText = defaults.getString("FileChooser.fileDescriptionText"); + + acceptAllFileFilterText = defaults.getString("FileChooser.acceptAllFileFilterText"); + cancelButtonText = "Cancel"; + cancelButtonToolTipText = "Abort file chooser dialog"; + cancelButtonMnemonic = new Integer((String) UIManager.get("FileChooser.cancelButtonMnemonic")).intValue(); + + directoryOpenButtonText = "Open"; + directoryOpenButtonToolTipText = "Open selected directory"; + directoryOpenButtonMnemonic + = new Integer((String) UIManager.get("FileChooser.directoryOpenButtonMnemonic")).intValue(); + + helpButtonText = "Help"; + helpButtonToolTipText = "FileChooser help"; + helpButtonMnemonic = new Integer((String) UIManager.get("FileChooser.helpButtonMnemonic")).intValue(); + + openButtonText = "Open"; + openButtonToolTipText = "Open selected file"; + openButtonMnemonic = new Integer((String) UIManager.get("FileChooser.openButtonMnemonic")).intValue(); + + saveButtonText = "Save"; + saveButtonToolTipText = "Save selected file"; + saveButtonMnemonic = new Integer((String) UIManager.get("FileChooser.saveButtonMnemonic")).intValue(); + + updateButtonText = "Update"; + updateButtonToolTipText = "Update directory listing"; + updateButtonMnemonic = new Integer((String) UIManager.get("FileChooser.updateButtonMnemonic")).intValue(); + } + + /** + * Uninstalls the strings previously added by this UI delegate. + * + * @param fc the file chooser. + */ + protected void uninstallStrings(JFileChooser fc) + { + acceptAllFileFilterText = null; + dirDescText = null; + fileDescText = null; + + cancelButtonText = null; + cancelButtonToolTipText = null; + + directoryOpenButtonText = null; + directoryOpenButtonToolTipText = null; + + helpButtonText = null; + helpButtonToolTipText = null; + + openButtonText = null; + openButtonToolTipText = null; + + saveButtonText = null; + saveButtonToolTipText = null; + + updateButtonText = null; + updateButtonToolTipText = null; + } + + /** + * Creates a new directory model. + */ + protected void createModel() + { + model = new BasicDirectoryModel(filechooser); + } + + /** + * Returns the directory model. + * + * @return The directory model. + */ + public BasicDirectoryModel getModel() + { + return model; + } + + /** + * Creates a listener to handle changes to the properties of the given + * file chooser component. + * + * @param fc the file chooser component. + * + * @return A new listener. + */ + public PropertyChangeListener createPropertyChangeListener(JFileChooser fc) + { + // The RI returns null here, so do we. + return null; + } + + /** + * Returns the current file name. + * + * @return The current file name. + */ + public String getFileName() + { + return entry.getText(); + } + + /** + * Returns the current directory name. + * + * @return The directory name. + * + * @see #setDirectoryName(String) + */ + public String getDirectoryName() + { + // XXX: I don't see a case where the thing returns something non-null.. + return null; + } + + /** + * Sets the file name. + * + * @param filename the file name. + * + * @see #getFileName() + */ + public void setFileName(String filename) + { + // FIXME: it might be the case that this method provides an access + // point for the JTextField (or whatever) a subclass is using... + //this.filename = filename; + } + + /** + * Sets the directory name (NOT IMPLEMENTED). + * + * @param dirname the directory name. + * + * @see #getDirectoryName() + */ + public void setDirectoryName(String dirname) + { + // FIXME: Implement + } + + /** + * Rescans the current directory. + * + * @param fc the file chooser. + */ + public void rescanCurrentDirectory(JFileChooser fc) + { + getModel().validateFileCache(); + } + + /** + * NOT YET IMPLEMENTED. + * + * @param fc the file chooser. + * @param f the file. + */ + public void ensureFileIsVisible(JFileChooser fc, File f) + { + // XXX: Not sure what this does. + } + + /** + * Returns the {@link JFileChooser} component that this UI delegate + * represents. + * + * @return The component represented by this UI delegate. + */ + public JFileChooser getFileChooser() + { + return filechooser; + } + + /** + * Returns the optional accessory panel. + * + * @return The optional accessory panel. + */ + public JPanel getAccessoryPanel() + { + return accessoryPanel; + } + + /** + * Returns the approve (open or save) button for the dialog. + * + * @param fc the file chooser. + * + * @return The button. + */ + protected JButton getApproveButton(JFileChooser fc) + { + return accept; + } + + /** + * Returns the tool tip text for the approve (open/save) button. This first + * checks the file chooser to see if a value has been explicitly set - if + * not, a default value appropriate for the type of file chooser is + * returned. + * + * @param fc the file chooser. + * + * @return The tool tip text. + */ + public String getApproveButtonToolTipText(JFileChooser fc) + { + if (fc.getApproveButtonToolTipText() != null) + return fc.getApproveButtonToolTipText(); + else if (fc.getDialogType() == JFileChooser.SAVE_DIALOG) + return saveButtonToolTipText; + else + return openButtonToolTipText; + } + + /** + * Clears the icon cache. + */ + public void clearIconCache() + { + if (fv instanceof BasicFileView) + ((BasicFileView) fv).clearIconCache(); + } + + /** + * Creates a new listener to handle selections in the file list. + * + * @param fc the file chooser component. + * + * @return A new instance of {@link SelectionListener}. + */ + public ListSelectionListener createListSelectionListener(JFileChooser fc) + { + return new SelectionListener(); + } + + /** + * Creates a new listener to handle double-click events. + * + * @param fc the file chooser component. + * @param list the list. + * + * @return A new instance of {@link DoubleClickListener}. + */ + protected MouseListener createDoubleClickListener(JFileChooser fc, JList list) + { + return new DoubleClickListener(list); + } + + /** + * Returns <code>true</code> if a directory is selected, and + * <code>false</code> otherwise. + * + * @return A boolean. + */ + protected boolean isDirectorySelected() + { + return dirSelected; + } + + /** + * Sets the flag that indicates whether the current directory is selected. + * + * @param selected the new flag value. + */ + protected void setDirectorySelected(boolean selected) + { + dirSelected = selected; + } + + /** + * Returns the current directory. + * + * @return The current directory. + */ + protected File getDirectory() + { + return currDir; + } + + /** + * Sets the current directory. + * + * @param f the directory. + */ + protected void setDirectory(File f) + { + currDir = f; + } + + /** + * Returns the "accept all" file filter. + * + * @param fc the file chooser component. + * + * @return The "accept all" file filter. + */ + public FileFilter getAcceptAllFileFilter(JFileChooser fc) + { + return acceptAll; + } + + /** + * Returns the default file view (NOT the file view from the file chooser, + * if there is one). + * + * @param fc the file chooser component. + * + * @return The file view. + * + * @see JFileChooser#getFileView() + */ + public FileView getFileView(JFileChooser fc) + { + return fv; + } + + /** + * Returns the dialog title. + * + * @param fc the file chooser (<code>null</code> not permitted). + * + * @return The dialog title. + * + * @see JFileChooser#getDialogTitle() + */ + public String getDialogTitle(JFileChooser fc) + { + String result = fc.getDialogTitle(); + if (result == null) + result = getApproveButtonText(fc); + return result; + } + + /** + * Returns the approve button mnemonic. + * + * @param fc the file chooser (<code>null</code> not permitted). + * + * @return The approve button mnemonic. + * + * @see JFileChooser#getApproveButtonMnemonic() + */ + public int getApproveButtonMnemonic(JFileChooser fc) + { + if (fc.getApproveButtonMnemonic() != 0) + return fc.getApproveButtonMnemonic(); + else if (fc.getDialogType() == JFileChooser.SAVE_DIALOG) + return saveButtonMnemonic; + else + return openButtonMnemonic; + } + + /** + * Returns the approve button text. + * + * @param fc the file chooser (<code>null</code> not permitted). + * + * @return The approve button text. + * + * @see JFileChooser#getApproveButtonText() + */ + public String getApproveButtonText(JFileChooser fc) + { + String result = fc.getApproveButtonText(); + if (result == null) + { + if (fc.getDialogType() == JFileChooser.SAVE_DIALOG) + result = saveButtonText; + else + result = openButtonText; + } + return result; + } + + /** + * Creates and returns a new action that will be used with the "new folder" + * button. + * + * @return A new instance of {@link NewFolderAction}. + */ + public Action getNewFolderAction() + { + if (newFolderAction == null) + newFolderAction = new NewFolderAction(); + return newFolderAction; + } + + /** + * Creates and returns a new action that will be used with the "home folder" + * button. + * + * @return A new instance of {@link GoHomeAction}. + */ + public Action getGoHomeAction() + { + if (goHomeAction == null) + goHomeAction = new GoHomeAction(); + return goHomeAction; + } + + /** + * Returns the action that handles events for the "up folder" control button. + * + * @return An instance of {@link ChangeToParentDirectoryAction}. + */ + public Action getChangeToParentDirectoryAction() + { + if (changeToParentDirectoryAction == null) + changeToParentDirectoryAction = new ChangeToParentDirectoryAction(); + return changeToParentDirectoryAction; + } + + /** + * Returns the action that handles events for the "approve" button. + * + * @return An instance of {@link ApproveSelectionAction}. + */ + public Action getApproveSelectionAction() + { + if (approveSelectionAction == null) + approveSelectionAction = new ApproveSelectionAction(); + return approveSelectionAction; + } + + /** + * Returns the action that handles events for the "cancel" button. + * + * @return An instance of {@link CancelSelectionAction}. + */ + public Action getCancelSelectionAction() + { + if (cancelSelectionAction == null) + cancelSelectionAction = new CancelSelectionAction(); + return cancelSelectionAction; + } + + /** + * Returns the update action (an instance of {@link UpdateAction}). + * + * @return An action. + */ + public Action getUpdateAction() + { + if (updateAction == null) + updateAction = new UpdateAction(); + return updateAction; + } +} diff --git a/libjava/classpath/javax/swing/plaf/basic/BasicFormattedTextFieldUI.java b/libjava/classpath/javax/swing/plaf/basic/BasicFormattedTextFieldUI.java new file mode 100644 index 000000000..555921435 --- /dev/null +++ b/libjava/classpath/javax/swing/plaf/basic/BasicFormattedTextFieldUI.java @@ -0,0 +1,69 @@ +/* BasicFormattedTextFieldUI.java + Copyright (C) 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 javax.swing.JComponent; +import javax.swing.UIDefaults; +import javax.swing.plaf.ComponentUI; + +/** + * @since 1.4 + */ +public class BasicFormattedTextFieldUI extends BasicTextFieldUI +{ + public BasicFormattedTextFieldUI() + { + // Nothing to do here. + } + + public static ComponentUI createUI(JComponent c) + { + return new BasicFormattedTextFieldUI(); + } + + /** + * Returns the prefix for entries in the {@link UIDefaults} table. + * + * @return "FormattedTextField" + */ + protected String getPropertyPrefix() + { + return "FormattedTextField"; + } +} 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 “bug ID” 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’s text and icon. + * + * @param b the button whose preferred size is determined. + * + * @param textIconGap the gap between the button’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); + } +} diff --git a/libjava/classpath/javax/swing/plaf/basic/BasicHTML.java b/libjava/classpath/javax/swing/plaf/basic/BasicHTML.java new file mode 100644 index 000000000..a7ce8b15b --- /dev/null +++ b/libjava/classpath/javax/swing/plaf/basic/BasicHTML.java @@ -0,0 +1,471 @@ +/* BasicHTML.java -- Provides HTML support to ComponentUI implementations + Copyright (C) 2006 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 java.awt.Container; +import java.awt.Graphics; +import java.awt.Rectangle; +import java.awt.Shape; +import java.io.IOException; +import java.io.StringReader; + +import javax.swing.JComponent; +import javax.swing.SwingConstants; +import javax.swing.event.DocumentEvent; +import javax.swing.text.AttributeSet; +import javax.swing.text.BadLocationException; +import javax.swing.text.Document; +import javax.swing.text.EditorKit; +import javax.swing.text.Element; +import javax.swing.text.Position; +import javax.swing.text.View; +import javax.swing.text.ViewFactory; +import javax.swing.text.html.HTMLDocument; +import javax.swing.text.html.HTMLEditorKit; + +/** + * Provides support for HTML rendering to {@link javax.swing.plaf.ComponentUI} + * implementations. + * + * @author Roman Kennke (kennke@aicas.com) + */ +public class BasicHTML +{ + + /** + * This class serves as the root view for HTML rendering components. + * Its purpose and implementation is similar to the BasicTextUI.RootView + * class, only that is implements some stuff differently due to the nature + * of not beeing inside a JTextComponent. + * + * @author Roman Kennke (kennke@aicas.com) + */ + private static class HTMLRootView extends View + { + /** + * The real root view. + */ + private View view; + + /** + * The component on which to render the view. + */ + private JComponent component; + + /** + * The EditorKit. + */ + private EditorKit editorKit; + + /** + * The document to use. + */ + private Document document; + + /** + * Creates a new RootView. + */ + public HTMLRootView(JComponent c, View view, EditorKit kit, Document doc) + { + super(null); + component = c; + editorKit = kit; + document = doc; + setView(view); + setSize(view.getPreferredSpan(X_AXIS), view.getPreferredSpan(Y_AXIS)); + } + + /** + * Returns the ViewFactory for this RootView. If the current EditorKit + * provides a ViewFactory, this is used. Otherwise the TextUI itself + * is returned as a ViewFactory. + * + * @return the ViewFactory for this RootView + */ + public ViewFactory getViewFactory() + { + return editorKit.getViewFactory(); + } + + /** + * Indicates that the preferences of one of the child view has changed. + * This calls revalidate on the text component. + * + * @param v the child view which's preference has changed + * @param width <code>true</code> if the width preference has changed + * @param height <code>true</code> if the height preference has changed + */ + public void preferenceChanged(View v, boolean width, boolean height) + { + component.revalidate(); + } + + /** + * Sets the real root view. + * + * @param v the root view to set + */ + public void setView(View v) + { + if (view != null) + view.setParent(null); + + if (v != null) + v.setParent(this); + + view = v; + } + + /** + * Overridden to forward to real view. + */ + public void setSize(float w, float h) + { + view.setSize(w, h); + } + + /** + * Returns the real root view, regardless of the index. + * + * @param index not used here + * + * @return the real root view, regardless of the index. + */ + public View getView(int index) + { + return view; + } + + /** + * Returns <code>1</code> since the RootView always contains one + * child, that is the real root of the View hierarchy. + * + * @return <code>1</code> since the RootView always contains one + * child, that is the real root of the View hierarchy + */ + public int getViewCount() + { + int count = 0; + if (view != null) + count = 1; + return count; + } + + /** + * Returns the <code>Container</code> that contains this view. This + * normally will be the text component that is managed by this TextUI. + * + * @return the <code>Container</code> that contains this view + */ + public Container getContainer() + { + return component; + } + + /** + * Returns the preferred span along the specified <code>axis</code>. + * This is delegated to the real root view. + * + * @param axis the axis for which the preferred span is queried + * + * @return the preferred span along the axis + */ + public float getPreferredSpan(int axis) + { + if (view != null) + return view.getPreferredSpan(axis); + + return Integer.MAX_VALUE; + } + + /** + * Paints the view. This is delegated to the real root view. + * + * @param g the <code>Graphics</code> context to paint to + * @param s the allocation for the View + */ + public void paint(Graphics g, Shape s) + { + if (view != null) + { + Rectangle b = s.getBounds(); + view.setSize(b.width, b.height); + view.paint(g, s); + } + } + + + /** + * Maps a position in the document into the coordinate space of the View. + * The output rectangle usually reflects the font height but has a width + * of zero. + * + * This is delegated to the real root view. + * + * @param position the position of the character in the model + * @param a the area that is occupied by the view + * @param bias either {@link Position.Bias#Forward} or + * {@link Position.Bias#Backward} depending on the preferred + * direction bias. If <code>null</code> this defaults to + * <code>Position.Bias.Forward</code> + * + * @return a rectangle that gives the location of the document position + * inside the view coordinate space + * + * @throws BadLocationException if <code>pos</code> is invalid + * @throws IllegalArgumentException if b is not one of the above listed + * valid values + */ + public Shape modelToView(int position, Shape a, Position.Bias bias) + throws BadLocationException + { + return view.modelToView(position, a, bias); + } + + /** + * Maps coordinates from the <code>View</code>'s space into a position + * in the document model. + * + * @param x the x coordinate in the view space + * @param y the y coordinate in the view space + * @param a the allocation of this <code>View</code> + * @param b the bias to use + * + * @return the position in the document that corresponds to the screen + * coordinates <code>x, y</code> + */ + public int viewToModel(float x, float y, Shape a, Position.Bias[] b) + { + return view.viewToModel(x, y, a, b); + } + + /** + * Notification about text insertions. These are forwarded to the + * real root view. + * + * @param ev the DocumentEvent describing the change + * @param shape the current allocation of the view's display + * @param vf the ViewFactory to use for creating new Views + */ + public void insertUpdate(DocumentEvent ev, Shape shape, ViewFactory vf) + { + view.insertUpdate(ev, shape, vf); + } + + /** + * Notification about text removals. These are forwarded to the + * real root view. + * + * @param ev the DocumentEvent describing the change + * @param shape the current allocation of the view's display + * @param vf the ViewFactory to use for creating new Views + */ + public void removeUpdate(DocumentEvent ev, Shape shape, ViewFactory vf) + { + view.removeUpdate(ev, shape, vf); + } + + /** + * Notification about text changes. These are forwarded to the + * real root view. + * + * @param ev the DocumentEvent describing the change + * @param shape the current allocation of the view's display + * @param vf the ViewFactory to use for creating new Views + */ + public void changedUpdate(DocumentEvent ev, Shape shape, ViewFactory vf) + { + view.changedUpdate(ev, shape, vf); + } + + /** + * Returns the document position that is (visually) nearest to the given + * document position <code>pos</code> in the given direction <code>d</code>. + * + * @param pos the document position + * @param b the bias for <code>pos</code> + * @param a the allocation for the view + * @param d the direction, must be either {@link SwingConstants#NORTH}, + * {@link SwingConstants#SOUTH}, {@link SwingConstants#WEST} or + * {@link SwingConstants#EAST} + * @param biasRet an array of {@link Position.Bias} that can hold at least + * one element, which is filled with the bias of the return position + * on method exit + * + * @return the document position that is (visually) nearest to the given + * document position <code>pos</code> in the given direction + * <code>d</code> + * + * @throws BadLocationException if <code>pos</code> is not a valid offset in + * the document model + */ + public int getNextVisualPositionFrom(int pos, Position.Bias b, Shape a, + int d, Position.Bias[] biasRet) + throws BadLocationException + { + return view.getNextVisualPositionFrom(pos, b, a, d, biasRet); + } + + public int getStartOffset() + { + return 0; + } + + public int getEndOffset() + { + return getDocument().getLength(); + } + + public Document getDocument() + { + return document; + } + + /** + * Overridden to return null, as a RootView has no attributes on its own. + */ + public AttributeSet getAttributes() + { + return null; + } + + /** + * Overridden to provide an element for the view. + */ + public Element getElement() + { + return view.getElement(); + } + } + + /** + * The key that is used to store a HTML view in a JComponent's client + * properties. + */ + public static final String propertyKey = "html"; + + /** + * The key that is used to store the document base in a JComponent's client + * properties. The document base is used to resolve relative references + * in HTML. + */ + public static final String documentBaseKey = "html.base"; + + /** + * Creates a new instance of BasicHTML. This should not be necessary since + * all methods in this class are static. + */ + public BasicHTML() + { + // Nothing to do here. + } + + /** + * Creates a {@link View} instance that can be used by the component + * <code>c</code> to render the HTML string <code>html</code>. + * + * @param c the component that needs to render the HTML string + * @param html the HTML string to be rendered + * + * @return a view that can render the HTML string + */ + public static View createHTMLView(JComponent c, String html) + { + // TODO: This might be wrong. Lets see if it turns out good when + // the javax.swing.text.html package is in a good shape. + HTMLDocument doc = new HTMLDocument(); + HTMLEditorKit kit = new HTMLEditorKit(); + StringReader reader = new StringReader(html); + try + { + kit.read(reader, doc, 0); + } + catch (IOException ex) + { + AssertionError err = new AssertionError("unexpected IOException"); + err.initCause(ex); + throw err; + } + catch (BadLocationException ex) + { + AssertionError err = + new AssertionError("unexpected BadLocationException"); + err.initCause(ex); + throw err; + } + ViewFactory vf = kit.getViewFactory(); + Element root = doc.getDefaultRootElement(); + View view = vf.create(root); + HTMLRootView rootView = new HTMLRootView(c, view, kit, doc); + return rootView; + } + + /** + * Returns <code>true</code> if <code>s</code> is HTML, <code>false</code> + * otherwise. + * + * @param s the string to test + * + * @return <code>true</code> if <code>s</code> is HTML, <code>false</code> + * otherwise + */ + public static boolean isHTMLString(String s) + { + // We consider a string to be HTML if it contains both the '<' and '>' + // character at least once. + return (s != null) && s.contains("<") && s.contains(">"); + } + + /** + * Stores a HTML renderer in <code>c</code>'s client property if + * <code>text</code> is HTML, otherwise it clears the corresponding client + * property. This is useful for {@link javax.swing.plaf.ComponentUI} + * implementations that are shared between it's components. + * + * @param c the component to update the renderer for + * @param text the string to be rendered + */ + public static void updateRenderer(JComponent c, String text) + { + if (isHTMLString(text)) + c.putClientProperty(propertyKey, createHTMLView(c, text)); + else + c.putClientProperty(propertyKey, null); + } +} diff --git a/libjava/classpath/javax/swing/plaf/basic/BasicIconFactory.java b/libjava/classpath/javax/swing/plaf/basic/BasicIconFactory.java new file mode 100644 index 000000000..1b5afa7f1 --- /dev/null +++ b/libjava/classpath/javax/swing/plaf/basic/BasicIconFactory.java @@ -0,0 +1,328 @@ +/* BasicIconFactory.java -- + Copyright (C) 2002, 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 java.awt.Color; +import java.awt.Component; +import java.awt.Graphics; +import java.io.Serializable; + +import javax.swing.Icon; +import javax.swing.JCheckBoxMenuItem; + +/** + * Creates icons for the {@link BasicLookAndFeel}. + */ +public class BasicIconFactory implements Serializable +{ + static final long serialVersionUID = 5605588811185324383L; + + private static class DummyIcon + implements Icon + { + public int getIconHeight() + { + return 10; + } + public int getIconWidth() + { + return 10; + } + public void paintIcon(Component c, Graphics g, int x, int y) + { + Color save = g.getColor(); + g.setColor(c.getForeground()); + g.drawRect(x, y, 10, 10); + g.setColor(save); + } + } + + /** + * The icon used for CheckBoxes in the BasicLookAndFeel. This is an empty + * icon with a size of 13x13 pixels. + */ + static class CheckBoxIcon + implements Icon + { + /** + * Returns the height of the icon. The BasicLookAndFeel CheckBox icon + * has a height of 13 pixels. + * + * @return the height of the icon + */ + public int getIconHeight() + { + return 13; + } + + /** + * Returns the width of the icon. The BasicLookAndFeel CheckBox icon + * has a width of 13 pixels. + * + * @return the height of the icon + */ + public int getIconWidth() + { + return 13; + } + + /** + * Paints the icon. The BasicLookAndFeel CheckBox icon is empty and does + * not need to be painted. + * + * @param c the component to be painted + * @param g the Graphics context to be painted with + * @param x the x position of the icon + * @param y the y position of the icon + */ + public void paintIcon(Component c, Graphics g, int x, int y) + { + // The icon is empty and needs no painting. + } + } + + /** + * The icon used for {@link JCheckBoxMenuItem}s in the + * {@link BasicLookAndFeel}. This icon has a size of 9x9 pixels. + */ + static class CheckBoxMenuItemIcon + implements Icon + { + /** + * Returns the height of the icon in pixels. + * + * @return the height of the icon + */ + public int getIconHeight() + { + return 9; + } + + /** + * Returns the width of the icon in pixels. + * + * @return the height of the icon + */ + public int getIconWidth() + { + return 9; + } + + /** + * Paints the icon. + * + * @param c the component to be painted + * @param g the Graphics context to be painted with + * @param x the x position of the icon + * @param y the y position of the icon + */ + public void paintIcon(Component c, Graphics g, int x, int y) + { + JCheckBoxMenuItem item = (JCheckBoxMenuItem) c; + if (item.isSelected()) + { + // paint the check... + g.setColor(Color.black); + g.drawLine(x + 1, y + 3, x + 1, y + 4); + g.drawLine(x + 2, y + 4, x + 2, y + 5); + for (int i = 0; i < 5; i++) + g.drawLine(x + 3 + i, y + 5 - i, x + 3 + i, y + 6 - i); + } + } + } + + /** + * The icon used for RadioButtons in the BasicLookAndFeel. This is an empty + * icon with a size of 13x13 pixels. + */ + static class RadioButtonIcon + implements Icon + { + /** + * Returns the height of the icon. The BasicLookAndFeel RadioButton icon + * has a height of 13 pixels. + * + * @return the height of the icon + */ + public int getIconHeight() + { + return 13; + } + + /** + * Returns the width of the icon. The BasicLookAndFeel RadioButton icon + * has a width of 13 pixels. + * + * @return the height of the icon + */ + public int getIconWidth() + { + return 13; + } + + /** + * Paints the icon. The BasicLookAndFeel RadioButton icon is empty and does + * not need to be painted. + * + * @param c the component to be painted + * @param g the Graphics context to be painted with + * @param x the x position of the icon + * @param y the y position of the icon + */ + public void paintIcon(Component c, Graphics g, int x, int y) + { + // The icon is empty and needs no painting. + } + } + /** The cached CheckBoxIcon instance. */ + private static CheckBoxIcon checkBoxIcon; + + /** The cached RadioButtonIcon instance. */ + private static RadioButtonIcon radioButtonIcon; + + public static Icon getMenuItemCheckIcon() + { + return new Icon() + { + public int getIconHeight() + { + return 13; + } + + public int getIconWidth() + { + return 13; + } + + public void paintIcon(Component c, Graphics g, int x, int y) + { + Color saved = g.getColor(); + g.setColor(Color.BLACK); + g.drawLine(3 + x, 5 + y, 3 + x, 9 + y); + g.drawLine(4 + x, 5 + y, 4 + x, 9 + y); + g.drawLine(5 + x, 7 + y, 9 + x, 3 + y); + g.drawLine(5 + x, 8 + y, 9 + x, 4 + y); + g.setColor(saved); + } + }; + } + public static Icon getMenuItemArrowIcon() + { + return new DummyIcon(); + } + + /** + * Returns a new instance of a 4 x 8 icon showing a small black triangle that + * points to the right. This is displayed in menu items that have a + * sub menu. + * + * @return The icon. + */ + public static Icon getMenuArrowIcon() + { + return new Icon() + { + public int getIconHeight() + { + return 8; + } + public int getIconWidth() + { + return 4; + } + public void paintIcon(Component c, Graphics g, int x, int y) + { + Color saved = g.getColor(); + g.setColor(Color.BLACK); + for (int i = 0; i < 4; i++) + g.drawLine(x + i, y + i, x + i, y + 7 - i); + g.setColor(saved); + } + }; + } + + /** + * Returns an icon for CheckBoxes in the BasicLookAndFeel. CheckBox icons + * in the Basic L&F are empty and have a size of 13x13 pixels. + * This method returns a shared single instance of this icon. + * + * @return an icon for CheckBoxes in the BasicLookAndFeel + */ + public static Icon getCheckBoxIcon() + { + if (checkBoxIcon == null) + checkBoxIcon = new CheckBoxIcon(); + return checkBoxIcon; + } + + /** + * Returns an icon for RadioButtons in the BasicLookAndFeel. RadioButton + * icons in the Basic L&F are empty and have a size of 13x13 pixels. + * This method returns a shared single instance of this icon. + * + * @return an icon for RadioButtons in the BasicLookAndFeel + */ + public static Icon getRadioButtonIcon() + { + if (radioButtonIcon == null) + radioButtonIcon = new RadioButtonIcon(); + return radioButtonIcon; + } + + /** + * Creates and returns an icon used when rendering {@link JCheckBoxMenuItem} + * components. + * + * @return An icon. + */ + public static Icon getCheckBoxMenuItemIcon() + { + return new CheckBoxMenuItemIcon(); + } + + public static Icon getRadioButtonMenuItemIcon() + { + return getRadioButtonIcon(); + } + + public static Icon createEmptyFrameIcon() + { + return new DummyIcon(); + } +} // class BasicIconFactory diff --git a/libjava/classpath/javax/swing/plaf/basic/BasicInternalFrameTitlePane.java b/libjava/classpath/javax/swing/plaf/basic/BasicInternalFrameTitlePane.java new file mode 100644 index 000000000..484660501 --- /dev/null +++ b/libjava/classpath/javax/swing/plaf/basic/BasicInternalFrameTitlePane.java @@ -0,0 +1,1015 @@ +/* BasicInternalFrameTitlePane.java -- + Copyright (C) 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 java.awt.Color; +import java.awt.Component; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.FontMetrics; +import java.awt.Graphics; +import java.awt.Insets; +import java.awt.LayoutManager; +import java.awt.Rectangle; +import java.awt.event.ActionEvent; +import java.awt.event.KeyEvent; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.beans.PropertyVetoException; + +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.Icon; +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JInternalFrame; +import javax.swing.JLabel; +import javax.swing.JMenu; +import javax.swing.JMenuBar; +import javax.swing.JMenuItem; +import javax.swing.SwingConstants; +import javax.swing.SwingUtilities; +import javax.swing.UIManager; + +/** + * This class acts as a titlebar for JInternalFrames. + */ +public class BasicInternalFrameTitlePane extends JComponent +{ + /** + * The Action responsible for closing the JInternalFrame. + * + * @specnote Apparently this class was intended to be protected, + * but was made public by a compiler bug and is now + * public for compatibility. + */ + public class CloseAction extends AbstractAction + { + /** + * Creates a new action. + */ + public CloseAction() + { + super("Close"); + } + + /** + * This method is called when something closes the JInternalFrame. + * + * @param e The ActionEvent. + */ + public void actionPerformed(ActionEvent e) + { + if (frame.isClosable()) + { + try + { + frame.setClosed(true); + } + catch (PropertyVetoException pve) + { + // We do nothing if the attempt has been vetoed. + } + } + } + } + + /** + * This Action is responsible for iconifying the JInternalFrame. + * + * @specnote Apparently this class was intended to be protected, + * but was made public by a compiler bug and is now + * public for compatibility. + */ + public class IconifyAction extends AbstractAction + { + /** + * Creates a new action. + */ + public IconifyAction() + { + super("Minimize"); + } + + /** + * This method is called when the user wants to iconify the + * JInternalFrame. + * + * @param e The ActionEvent. + */ + public void actionPerformed(ActionEvent e) + { + if (frame.isIconifiable() && ! frame.isIcon()) + { + try + { + frame.setIcon(true); + } + catch (PropertyVetoException pve) + { + // We do nothing if the attempt has been vetoed. + } + } + } + } + + /** + * This Action is responsible for maximizing the JInternalFrame. + * + * @specnote Apparently this class was intended to be protected, + * but was made public by a compiler bug and is now + * public for compatibility. + */ + public class MaximizeAction extends AbstractAction + { + /** + * Creates a new action. + */ + public MaximizeAction() + { + super("Maximize"); + } + /** + * This method is called when the user wants to maximize the + * JInternalFrame. + * + * @param e The ActionEvent. + */ + public void actionPerformed(ActionEvent e) + { + try + { + if (frame.isMaximizable() && ! frame.isMaximum()) + { + frame.setMaximum(true); + maxButton.setIcon(minIcon); + } + else if (frame.isMaximum()) + { + frame.setMaximum(false); + maxButton.setIcon(maxIcon); + } + } + catch (PropertyVetoException pve) + { + // We do nothing if the attempt has been vetoed. + } + } + } + + /** + * This Action is responsible for dragging the JInternalFrame. + * + * @specnote Apparently this class was intended to be protected, + * but was made public by a compiler bug and is now + * public for compatibility. + */ + public class MoveAction extends AbstractAction + { + /** + * Creates a new action. + */ + public MoveAction() + { + super("Move"); + } + /** + * This method is called when the user wants to drag the JInternalFrame. + * + * @param e The ActionEvent. + */ + public void actionPerformed(ActionEvent e) + { + // FIXME: Implement keyboard driven? move actions. + } + } + + /** + * This Action is responsible for restoring the JInternalFrame. Restoring + * the JInternalFrame is the same as setting the maximum property to false. + * + * @specnote Apparently this class was intended to be protected, + * but was made public by a compiler bug and is now + * public for compatibility. + */ + public class RestoreAction extends AbstractAction + { + /** + * Creates a new action. + */ + public RestoreAction() + { + super("Restore"); + } + /** + * This method is called when the user wants to restore the + * JInternalFrame. + * + * @param e The ActionEvent. + */ + public void actionPerformed(ActionEvent e) + { + if (frame.isMaximum()) + { + try + { + frame.setMaximum(false); + } + catch (PropertyVetoException pve) + { + // We do nothing if the attempt has been vetoed. + } + } + } + } + + /** + * This action is responsible for sizing the JInternalFrame. + * + * @specnote Apparently this class was intended to be protected, + * but was made public by a compiler bug and is now + * public for compatibility. + */ + public class SizeAction extends AbstractAction + { + /** + * Creates a new action. + */ + public SizeAction() + { + super("Size"); + } + /** + * This method is called when the user wants to resize the JInternalFrame. + * + * @param e The ActionEvent. + */ + public void actionPerformed(ActionEvent e) + { + // FIXME: Not sure how size actions should be handled. + } + } + + /** + * This class is responsible for handling property change events from the + * JInternalFrame and adjusting the Title Pane as necessary. + * + * @specnote Apparently this class was intended to be protected, + * but was made public by a compiler bug and is now + * public for compatibility. + */ + public class PropertyChangeHandler implements PropertyChangeListener + { + /** + * This method is called when a PropertyChangeEvent is received by the + * Title Pane. + * + * @param evt The PropertyChangeEvent. + */ + public void propertyChange(PropertyChangeEvent evt) + { + String propName = evt.getPropertyName(); + if (propName.equals("closable")) + { + if (evt.getNewValue().equals(Boolean.TRUE)) + closeButton.setVisible(true); + else + closeButton.setVisible(false); + } + else if (propName.equals("iconable")) + { + if (evt.getNewValue().equals(Boolean.TRUE)) + iconButton.setVisible(true); + else + iconButton.setVisible(false); + } + else if (propName.equals("maximizable")) + { + if (evt.getNewValue().equals(Boolean.TRUE)) + maxButton.setVisible(true); + else + maxButton.setVisible(false); + } + enableActions(); + } + } + + /** + * This class acts as the MenuBar for the TitlePane. Clicking on the Frame + * Icon in the top left corner will activate it. + * + * @specnote Apparently this class was intended to be protected, + * but was made public by a compiler bug and is now + * public for compatibility. + */ + public class SystemMenuBar extends JMenuBar + { + /** + * This method returns true if it can receive focus. + * + * @return True if this Component can receive focus. + */ + public boolean isFocusTraversable() + { + return true; + } + + /** + * This method returns true if this Component is expected to paint all of + * itself. + * + * @return True if this Component is expect to paint all of itself. + */ + public boolean isOpaque() + { + return true; + } + + /** + * This method paints this Component. + * + * @param g The Graphics object to paint with. + */ + public void paint(Graphics g) + { + Icon frameIcon = frame.getFrameIcon(); + if (frameIcon == null) + frameIcon = BasicDesktopIconUI.defaultIcon; + frameIcon.paintIcon(this, g, 0, 0); + } + + /** + * This method requests that focus be given to this Component. + */ + public void requestFocus() + { + super.requestFocus(); + } + } + + /** + * This class acts as the Layout Manager for the TitlePane. + * + * @specnote Apparently this class was intended to be protected, + * but was made public by a compiler bug and is now + * public for compatibility. + */ + public class TitlePaneLayout implements LayoutManager + { + /** + * Creates a new <code>TitlePaneLayout</code> object. + */ + public TitlePaneLayout() + { + // Do nothing. + } + + /** + * This method is called when adding a Component to the Container. + * + * @param name The name to reference the added Component by. + * @param c The Component to add. + */ + public void addLayoutComponent(String name, Component c) + { + // Do nothing. + } + + /** + * This method is called to lay out the children of the Title Pane. + * + * @param c The Container to lay out. + */ + public void layoutContainer(Container c) + { + Dimension size = c.getSize(); + Insets insets = c.getInsets(); + int width = size.width - insets.left - insets.right; + int height = size.height - insets.top - insets.bottom; + + // MenuBar is always present and located at the top left corner. + Dimension menupref = menuBar.getPreferredSize(); + menuBar.setBounds(insets.left, insets.top, menupref.width, height); + + int loc = width + insets.left - 1; + int top = insets.top + 1; + int buttonHeight = height - 4; + if (closeButton.isVisible()) + { + int buttonWidth = closeIcon.getIconWidth(); + loc -= buttonWidth + 2; + closeButton.setBounds(loc, top, buttonWidth, buttonHeight); + } + + if (maxButton.isVisible()) + { + int buttonWidth = maxIcon.getIconWidth(); + loc -= buttonWidth + 2; + maxButton.setBounds(loc, top, buttonWidth, buttonHeight); + } + + if (iconButton.isVisible()) + { + int buttonWidth = iconIcon.getIconWidth(); + loc -= buttonWidth + 2; + iconButton.setBounds(loc, top, buttonWidth, buttonHeight); + } + + if (title != null) + title.setBounds(insets.left + menupref.width, insets.top, + loc - menupref.width - insets.left, height); + } + + /** + * This method returns the minimum size of the given Container given the + * children that it has. + * + * @param c The Container to get a minimum size for. + * + * @return The minimum size of the Container. + */ + public Dimension minimumLayoutSize(Container c) + { + return preferredLayoutSize(c); + } + + /** + * This method returns the preferred size of the given Container taking + * into account the children that it has. + * + * @param c The Container to lay out. + * + * @return The preferred size of the Container. + */ + public Dimension preferredLayoutSize(Container c) + { + return new Dimension(22, 18); + } + + /** + * This method is called when removing a Component from the Container. + * + * @param c The Component to remove. + */ + public void removeLayoutComponent(Component c) + { + // Nothing to do here. + } + } + + /** + * This helper class is used to create the minimize, maximize and close + * buttons in the top right corner of the Title Pane. These buttons are + * special since they cannot be given focus and have no border. + */ + private class PaneButton extends JButton + { + /** + * Creates a new PaneButton object with the given Action. + * + * @param a The Action that the button uses. + */ + public PaneButton(Action a) + { + super(a); + setMargin(new Insets(0, 0, 0, 0)); + } + + /** + * This method returns true if the Component can be focused. + * + * @return false. + */ + public boolean isFocusable() + { + // These buttons cannot be given focus. + return false; + } + + } + + /** The action command for the Close action. */ + protected static final String CLOSE_CMD; + + /** The action command for the Minimize action. */ + protected static final String ICONIFY_CMD; + + /** The action command for the Maximize action. */ + protected static final String MAXIMIZE_CMD; + + /** The action command for the Move action. */ + protected static final String MOVE_CMD; + + /** The action command for the Restore action. */ + protected static final String RESTORE_CMD; + + /** The action command for the Size action. */ + protected static final String SIZE_CMD; + + /** The action associated with closing the JInternalFrame. */ + protected Action closeAction; + + /** The action associated with iconifying the JInternalFrame. */ + protected Action iconifyAction; + + /** The action associated with maximizing the JInternalFrame. */ + protected Action maximizeAction; + + /** The action associated with moving the JInternalFrame. */ + protected Action moveAction; + + /** The action associated with restoring the JInternalFrame. */ + protected Action restoreAction; + + /** The action associated with resizing the JInternalFrame. */ + protected Action sizeAction; + + /** The button that closes the JInternalFrame. */ + protected JButton closeButton; + + /** The button that iconifies the JInternalFrame. */ + protected JButton iconButton; + + /** The button that maximizes the JInternalFrame. */ + protected JButton maxButton; + + /** The icon displayed in the restore button. */ + protected Icon minIcon = BasicIconFactory.createEmptyFrameIcon(); + + /** The icon displayed in the maximize button. */ + protected Icon maxIcon = BasicIconFactory.createEmptyFrameIcon(); + + /** The icon displayed in the iconify button. */ + protected Icon iconIcon = BasicIconFactory.createEmptyFrameIcon(); + + /** The icon displayed in the close button. */ + protected Icon closeIcon; + + /** The JInternalFrame that this TitlePane is used in. */ + protected JInternalFrame frame; + + /** The JMenuBar that is located at the top left of the Title Pane. */ + protected JMenuBar menuBar; + + /** The JMenu inside the menuBar. */ + protected JMenu windowMenu; + + /** + * The text color of the TitlePane when the JInternalFrame is not selected. + */ + protected Color notSelectedTextColor; + + /** + * The background color of the TitlePane when the JInternalFrame is not + * selected. + */ + protected Color notSelectedTitleColor; + + /** The text color of the titlePane when the JInternalFrame is selected. */ + protected Color selectedTextColor; + + /** + * The background color of the TitlePane when the JInternalFrame is + * selected. + */ + protected Color selectedTitleColor; + + /** The Property Change listener that listens to the JInternalFrame. */ + protected PropertyChangeListener propertyChangeListener; + + /** + * The label used to display the title. This label is not added to the + * TitlePane. + * This is package-private to avoid an accessor method. + */ + transient JLabel title; + + static + { + // not constants in JDK + CLOSE_CMD = "Close"; + ICONIFY_CMD = "Minimize"; + MAXIMIZE_CMD = "Maximize"; + MOVE_CMD = "Move"; + RESTORE_CMD = "Restore"; + SIZE_CMD = "Size"; + } + + /** + * Creates a new BasicInternalFrameTitlePane object that is used in the + * given JInternalFrame. + * + * @param f The JInternalFrame this BasicInternalFrameTitlePane will be used + * in. + */ + public BasicInternalFrameTitlePane(JInternalFrame f) + { + frame = f; + setLayout(createLayout()); + title = new JLabel(); + title.setHorizontalAlignment(SwingConstants.LEFT); + title.setHorizontalTextPosition(SwingConstants.LEFT); + title.setOpaque(false); + setOpaque(true); + + setBackground(Color.LIGHT_GRAY); + setOpaque(true); + + installTitlePane(); + } + + /** + * This method installs the TitlePane onto the JInternalFrameTitlePane. It + * also creates any children components that need to be created and adds + * listeners to the appropriate components. + */ + protected void installTitlePane() + { + installDefaults(); + installListeners(); + createActions(); + + assembleSystemMenu(); + + createButtons(); + setButtonIcons(); + addSubComponents(); + enableActions(); + } + + /** + * This method adds the sub components to the TitlePane. + */ + protected void addSubComponents() + { + add(menuBar); + + add(closeButton); + add(iconButton); + add(maxButton); + } + + /** + * This method creates the actions that are used to manipulate the + * JInternalFrame. + */ + protected void createActions() + { + closeAction = new CloseAction(); + closeAction.putValue(AbstractAction.ACTION_COMMAND_KEY, CLOSE_CMD); + + iconifyAction = new IconifyAction(); + iconifyAction.putValue(AbstractAction.ACTION_COMMAND_KEY, ICONIFY_CMD); + + maximizeAction = new MaximizeAction(); + maximizeAction.putValue(AbstractAction.ACTION_COMMAND_KEY, MAXIMIZE_CMD); + + sizeAction = new SizeAction(); + sizeAction.putValue(AbstractAction.ACTION_COMMAND_KEY, SIZE_CMD); + + restoreAction = new RestoreAction(); + restoreAction.putValue(AbstractAction.ACTION_COMMAND_KEY, RESTORE_CMD); + + moveAction = new MoveAction(); + moveAction.putValue(AbstractAction.ACTION_COMMAND_KEY, MOVE_CMD); + } + + /** + * This method is used to install the listeners. + */ + protected void installListeners() + { + propertyChangeListener = createPropertyChangeListener(); + frame.addPropertyChangeListener(propertyChangeListener); + } + + /** + * This method is used to uninstall the listeners. + */ + protected void uninstallListeners() + { + frame.removePropertyChangeListener(propertyChangeListener); + propertyChangeListener = null; + } + + /** + * This method installs the defaults determined by the look and feel. + */ + protected void installDefaults() + { + title.setFont(UIManager.getFont("InternalFrame.titleFont")); + selectedTextColor = UIManager.getColor("InternalFrame.activeTitleForeground"); + selectedTitleColor = UIManager.getColor("InternalFrame.activeTitleBackground"); + notSelectedTextColor = UIManager.getColor("InternalFrame.inactiveTitleForeground"); + notSelectedTitleColor = UIManager.getColor("InternalFrame.inactiveTitleBackground"); + + closeIcon = UIManager.getIcon("InternalFrame.closeIcon"); + iconIcon = UIManager.getIcon("InternalFrame.iconifyIcon"); + maxIcon = UIManager.getIcon("InternalFrame.maximizeIcon"); + } + + /** + * This method uninstalls the defaults. + */ + protected void uninstallDefaults() + { + setFont(null); + selectedTextColor = null; + selectedTitleColor = null; + notSelectedTextColor = null; + notSelectedTitleColor = null; + + closeIcon = null; + iconIcon = null; + maxIcon = null; + } + + /** + * This method creates the buttons used in the TitlePane. + */ + protected void createButtons() + { + closeButton = new PaneButton(closeAction); + closeButton.setText(null); + if (!frame.isClosable()) + closeButton.setVisible(false); + iconButton = new PaneButton(iconifyAction); + iconButton.setText(null); + if (!frame.isIconifiable()) + iconButton.setVisible(false); + maxButton = new PaneButton(maximizeAction); + maxButton.setText(null); + if (!frame.isMaximizable()) + maxButton.setVisible(false); + } + + /** + * Set icons for the minimize-, maximize- and close-buttons. + */ + protected void setButtonIcons() + { + if (closeIcon != null && closeButton != null) + closeButton.setIcon(closeIcon); + if (iconIcon != null && iconButton != null) + iconButton.setIcon(iconIcon); + if (maxIcon != null && maxButton != null) + maxButton.setIcon(maxIcon); + } + + /** + * This method creates the MenuBar used in the TitlePane. + */ + protected void assembleSystemMenu() + { + menuBar = createSystemMenuBar(); + windowMenu = createSystemMenu(); + + menuBar.add(windowMenu); + + addSystemMenuItems(windowMenu); + enableActions(); + } + + /** + * This method adds the MenuItems to the given JMenu. + * + * @param systemMenu The JMenu to add MenuItems to. + */ + protected void addSystemMenuItems(JMenu systemMenu) + { + JMenuItem tmp; + + tmp = new JMenuItem(RESTORE_CMD); + tmp.addActionListener(restoreAction); + tmp.setMnemonic(KeyEvent.VK_R); + systemMenu.add(tmp); + + tmp = new JMenuItem(MOVE_CMD); + tmp.addActionListener(moveAction); + tmp.setMnemonic(KeyEvent.VK_M); + systemMenu.add(tmp); + + tmp = new JMenuItem(SIZE_CMD); + tmp.addActionListener(sizeAction); + tmp.setMnemonic(KeyEvent.VK_S); + systemMenu.add(tmp); + + tmp = new JMenuItem(ICONIFY_CMD); + tmp.addActionListener(iconifyAction); + tmp.setMnemonic(KeyEvent.VK_N); + systemMenu.add(tmp); + + tmp = new JMenuItem(MAXIMIZE_CMD); + tmp.addActionListener(maximizeAction); + tmp.setMnemonic(KeyEvent.VK_X); + systemMenu.add(tmp); + + systemMenu.addSeparator(); + + tmp = new JMenuItem(CLOSE_CMD); + tmp.addActionListener(closeAction); + tmp.setMnemonic(KeyEvent.VK_C); + systemMenu.add(tmp); + } + + /** + * This method creates a new JMenubar. + * + * @return A new JMenuBar. + */ + protected JMenuBar createSystemMenuBar() + { + if (menuBar == null) + menuBar = new SystemMenuBar(); + menuBar.removeAll(); + return menuBar; + } + + /** + * This method creates a new JMenu. + * + * @return A new JMenu. + */ + protected JMenu createSystemMenu() + { + if (windowMenu == null) + windowMenu = new JMenu(); + windowMenu.removeAll(); + return windowMenu; + } + + /** + * This method programmatically shows the JMenu. + */ + protected void showSystemMenu() + { + // FIXME: Untested as KeyEvents are not hooked up. + menuBar.getMenu(1).getPopupMenu().show(); + } + + /** + * This method paints the TitlePane. + * + * @param g The Graphics object to paint with. + */ + public void paintComponent(Graphics g) + { + paintTitleBackground(g); + if (frame.getTitle() != null && title != null) + { + Color saved = g.getColor(); + Font f = title.getFont(); + g.setFont(f); + FontMetrics fm = g.getFontMetrics(f); + if (frame.isSelected()) + g.setColor(selectedTextColor); + else + g.setColor(notSelectedTextColor); + title.setText(getTitle(frame.getTitle(), fm, title.getBounds().width)); + SwingUtilities.paintComponent(g, title, null, title.getBounds()); + g.setColor(saved); + } + } + + /** + * This method paints the TitlePane's background. + * + * @param g The Graphics object to paint with. + */ + protected void paintTitleBackground(Graphics g) + { + if (!isOpaque()) + return; + + Color saved = g.getColor(); + Dimension dims = getSize(); + + Color bg = getBackground(); + if (frame.isSelected()) + bg = selectedTitleColor; + else + bg = notSelectedTitleColor; + g.setColor(bg); + g.fillRect(0, 0, dims.width, dims.height); + g.setColor(saved); + } + + /** + * This method returns the title string based on the available width and the + * font metrics. + * + * @param text The desired title. + * @param fm The FontMetrics of the font used. + * @param availableWidth The available width. + * + * @return The allowable string. + */ + protected String getTitle(String text, FontMetrics fm, int availableWidth) + { + Rectangle vr = new Rectangle(0, 0, availableWidth, fm.getHeight()); + Rectangle ir = new Rectangle(); + Rectangle tr = new Rectangle(); + String value = SwingUtilities.layoutCompoundLabel(this, fm, text, null, + SwingConstants.CENTER, + SwingConstants.LEFT, + SwingConstants.CENTER, + SwingConstants.LEFT, vr, + ir, tr, 0); + return value; + } + + /** + * This method fires something similar to a WINDOW_CLOSING event. + * + * @param frame The JInternalFrame that is being closed. + */ + protected void postClosingEvent(JInternalFrame frame) + { + // FIXME: Implement postClosingEvent when I figure out what + // it's supposed to do. + // It says that this fires an WINDOW_CLOSING like event. + // So the closest thing is some kind of InternalFrameEvent. + // But none is fired. + // Can't see it called or anything. + } + + /** + * This method enables the actions for the TitlePane given the frame's + * properties. + */ + protected void enableActions() + { + closeAction.setEnabled(frame.isClosable()); + + iconifyAction.setEnabled(frame.isIconifiable()); + // The maximize action is responsible for restoring it + // as well, if clicked from the button + maximizeAction.setEnabled(frame.isMaximizable()); + + // The restoring action is only active when selected + // from the menu. + restoreAction.setEnabled(frame.isMaximum()); + + sizeAction.setEnabled(frame.isResizable()); + + // FIXME: Tie MoveAction enabled status to a variable. + moveAction.setEnabled(false); + } + + /** + * This method creates a new PropertyChangeListener. + * + * @return A new PropertyChangeListener. + */ + protected PropertyChangeListener createPropertyChangeListener() + { + return new PropertyChangeHandler(); + } + + /** + * This method creates a new LayoutManager for the TitlePane. + * + * @return A new LayoutManager. + */ + protected LayoutManager createLayout() + { + return new TitlePaneLayout(); + } +} diff --git a/libjava/classpath/javax/swing/plaf/basic/BasicInternalFrameUI.java b/libjava/classpath/javax/swing/plaf/basic/BasicInternalFrameUI.java new file mode 100644 index 000000000..da37e2bd8 --- /dev/null +++ b/libjava/classpath/javax/swing/plaf/basic/BasicInternalFrameUI.java @@ -0,0 +1,1786 @@ +/* BasicInternalFrameUI.java -- + Copyright (C) 2004, 2005, 2006 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 java.awt.Color; +import java.awt.Component; +import java.awt.Container; +import java.awt.Cursor; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Insets; +import java.awt.LayoutManager; +import java.awt.LayoutManager2; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.event.ActionEvent; +import java.awt.event.ComponentEvent; +import java.awt.event.ComponentListener; +import java.awt.event.MouseEvent; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.beans.PropertyVetoException; + +import javax.swing.AbstractAction; +import javax.swing.ActionMap; +import javax.swing.DefaultDesktopManager; +import javax.swing.DesktopManager; +import javax.swing.JComponent; +import javax.swing.JDesktopPane; +import javax.swing.JInternalFrame; +import javax.swing.KeyStroke; +import javax.swing.LookAndFeel; +import javax.swing.SwingConstants; +import javax.swing.SwingUtilities; +import javax.swing.UIManager; +import javax.swing.border.AbstractBorder; +import javax.swing.event.InternalFrameEvent; +import javax.swing.event.InternalFrameListener; +import javax.swing.event.MouseInputAdapter; +import javax.swing.event.MouseInputListener; +import javax.swing.plaf.ActionMapUIResource; +import javax.swing.plaf.ComponentUI; +import javax.swing.plaf.InternalFrameUI; +import javax.swing.plaf.UIResource; + +/** + * This is the UI delegate for the Basic look and feel for JInternalFrames. + */ +public class BasicInternalFrameUI extends InternalFrameUI +{ + /** + * This is a helper class that listens to the JInternalFrame for + * InternalFrameEvents. + */ + protected class BasicInternalFrameListener implements InternalFrameListener + { + /** + * This method is called when the JInternalFrame is activated. + * + * @param e The InternalFrameEvent. + */ + public void internalFrameActivated(InternalFrameEvent e) + { + frame.getGlassPane().setVisible(false); + } + + /** + * This method is called when the JInternalFrame is closed. + * + * @param e The InternalFrameEvent. + */ + public void internalFrameClosed(InternalFrameEvent e) + { + // FIXME: Implement. + } + + /** + * This method is called when the JInternalFrame is closing. + * + * @param e The InternalFrameEvent. + */ + public void internalFrameClosing(InternalFrameEvent e) + { + // FIXME: Implement. + } + + /** + * This method is called when the JInternalFrame is deactivated. + * + * @param e The InternalFrameEvent. + */ + public void internalFrameDeactivated(InternalFrameEvent e) + { + frame.getGlassPane().setVisible(true); + } + + /** + * This method is called when the JInternalFrame is deiconified. + * + * @param e The InternalFrameEvent. + */ + public void internalFrameDeiconified(InternalFrameEvent e) + { + // FIXME: Implement. + } + + /** + * This method is called when the JInternalFrame is iconified. + * + * @param e The InternalFrameEvent. + */ + public void internalFrameIconified(InternalFrameEvent e) + { + // FIXME: Implement. + } + + /** + * This method is called when the JInternalFrame is opened. + * + * @param e The InternalFrameEvent. + */ + public void internalFrameOpened(InternalFrameEvent e) + { + // FIXME: Implement. + } + } + + /** + * This helper class listens to the edges of the JInternalFrame and the + * TitlePane for mouse events. It is responsible for dragging and resizing + * the JInternalFrame in response to the MouseEvents. + */ + protected class BorderListener extends MouseInputAdapter + implements SwingConstants + { + /** + * The current shape of the cursor. + */ + transient int showingCursor; + + /** FIXME: Use for something. */ + protected final int RESIZE_NONE = 0; + + /** The x offset from the top left corner of the JInternalFrame. */ + private transient int xOffset; + + /** The y offset from the top left corner of the JInternalFrame. */ + private transient int yOffset; + + /** The direction that the resize is occuring in. */ + private transient int direction = -1; + + /** Cache rectangle that can be reused. */ + private transient Rectangle cacheRect = new Rectangle(); + + /** + * This method is called when the mouse is clicked. + * + * @param e The MouseEvent. + */ + public void mouseClicked(MouseEvent e) + { + // Do minimization/maximization when double-clicking in the title pane. + if (e.getSource() == titlePane && e.getClickCount() == 2) + try + { + if (frame.isMaximizable() && ! frame.isMaximum()) + frame.setMaximum(true); + else if (frame.isMaximum()) + frame.setMaximum(false); + } + catch (PropertyVetoException pve) + { + // We do nothing if the attempt has been vetoed. + } + + // There is nothing to do when the mouse is clicked + // on the border. + } + + /** + * This method is called when the mouse is dragged. This method is + * responsible for resizing or dragging the JInternalFrame. + * + * @param e The MouseEvent. + */ + public void mouseDragged(MouseEvent e) + { + // If the frame is maximized, there is nothing that + // can be dragged around. + if (frame.isMaximum()) + return; + DesktopManager dm = getDesktopManager(); + Rectangle b = frame.getBounds(); + Dimension min = frame.getMinimumSize(); + if (min == null) + min = new Dimension(0, 0); + Insets insets = frame.getInsets(); + int x = e.getX(); + int y = e.getY(); + if (e.getSource() == frame && frame.isResizable()) + { + switch (direction) + { + case Cursor.N_RESIZE_CURSOR: + cacheRect.setBounds(b.x, Math.min(b.y + y, b.y + b.height + - min.height), + b.width, b.height - y); + break; + case Cursor.NE_RESIZE_CURSOR: + cacheRect.setBounds(b.x, Math.min(b.y + y, b.y + b.height + - min.height), x + 1, + b.height - y); + break; + case Cursor.E_RESIZE_CURSOR: + cacheRect.setBounds(b.x, b.y, x + 1, b.height); + break; + case Cursor.SE_RESIZE_CURSOR: + cacheRect.setBounds(b.x, b.y, x + 1, y + 1); + break; + case Cursor.S_RESIZE_CURSOR: + cacheRect.setBounds(b.x, b.y, b.width, y + 1); + break; + case Cursor.SW_RESIZE_CURSOR: + cacheRect.setBounds(Math.min(b.x + x, b.x + b.width - min.width), + b.y, b.width - x, y + 1); + break; + case Cursor.W_RESIZE_CURSOR: + cacheRect.setBounds(Math.min(b.x + x, b.x + b.width - min.width), + b.y, b.width - x, b.height); + break; + case Cursor.NW_RESIZE_CURSOR: + cacheRect.setBounds( + Math.min(b.x + x, b.x + b.width - min.width), + Math.min(b.y + y, b.y + b.height - min.height), + b.width - x, b.height - y); + break; + } + dm.resizeFrame(frame, cacheRect.x, cacheRect.y, + Math.max(min.width, cacheRect.width), + Math.max(min.height, cacheRect.height)); + setCursor(e); + } + else if (e.getSource() == titlePane) + { + Rectangle fBounds = frame.getBounds(); + frame.putClientProperty("bufferedDragging", Boolean.TRUE); + dm.dragFrame(frame, e.getX() - xOffset + b.x, e.getY() - yOffset + + b.y); + } + } + + /** + * This method is called when the mouse exits the JInternalFrame. + * + * @param e The MouseEvent. + */ + public void mouseExited(MouseEvent e) + { + if (showingCursor != Cursor.DEFAULT_CURSOR) + { + frame.setCursor(Cursor.getDefaultCursor()); + showingCursor = Cursor.DEFAULT_CURSOR; + } + } + + /** + * This method is called when the mouse is moved inside the JInternalFrame. + * + * @param e The MouseEvent. + */ + public void mouseMoved(MouseEvent e) + { + // Turn off the resize cursor if we are in the frame header. + if (showingCursor != Cursor.DEFAULT_CURSOR && e.getSource() != frame) + { + frame.setCursor(Cursor.getDefaultCursor()); + showingCursor = Cursor.DEFAULT_CURSOR; + } + else if (e.getSource() == frame && frame.isResizable()) + { + setCursor(e); + } + } + + /** + * Set the mouse cursor, how applicable. + * + * @param e the current mouse event. + */ + void setCursor(MouseEvent e) + { + int cursor = sectionOfClick(e.getX(), e.getY()); + if (cursor != showingCursor) + { + Cursor resize = Cursor.getPredefinedCursor(cursor); + frame.setCursor(resize); + showingCursor = cursor; + } + } + + /** + * This method is called when the mouse is pressed. + * + * @param e The MouseEvent. + */ + public void mousePressed(MouseEvent e) + { + activateFrame(frame); + DesktopManager dm = getDesktopManager(); + int x = e.getX(); + int y = e.getY(); + Insets insets = frame.getInsets(); + + if (e.getSource() == frame && frame.isResizable()) + { + direction = sectionOfClick(x, y); + dm.beginResizingFrame(frame, direction); + } + else if (e.getSource() == titlePane) + { + Rectangle tBounds = titlePane.getBounds(); + + xOffset = e.getX() - tBounds.x + insets.left; + yOffset = e.getY() - tBounds.y + insets.top; + + dm.beginDraggingFrame(frame); + } + } + + /** + * This method is called when the mouse is released. + * + * @param e The MouseEvent. + */ + public void mouseReleased(MouseEvent e) + { + DesktopManager dm = getDesktopManager(); + xOffset = 0; + yOffset = 0; + if (e.getSource() == frame && frame.isResizable()) + dm.endResizingFrame(frame); + else if (e.getSource() == titlePane) + { + dm.endDraggingFrame(frame); + frame.putClientProperty("bufferedDragging", null); + } + + setCursor(e); + } + + /** + * This method determines the direction of the resize based on the + * coordinates and the size of the JInternalFrame. + * + * @param x The x coordinate of the MouseEvent. + * @param y The y coordinate of the MouseEvent. + * + * @return The cursor constant, determining the resizing direction. + */ + private int sectionOfClick(int x, int y) + { + Rectangle b = frame.getBounds(); + int corner = InternalFrameBorder.cornerSize; + + if (x < corner && y < corner) + return Cursor.NW_RESIZE_CURSOR; + else if (x > b.width - corner && y < corner) + return Cursor.NE_RESIZE_CURSOR; + else if (x > b.width - corner && y > b.height - corner) + return Cursor.SE_RESIZE_CURSOR; + else if (x < corner && y > b.height - corner) + return Cursor.SW_RESIZE_CURSOR; + else if (y < corner) + return Cursor.N_RESIZE_CURSOR; + else if (x < corner) + return Cursor.W_RESIZE_CURSOR; + else if (y > b.height - corner) + return Cursor.S_RESIZE_CURSOR; + else if (x > b.width - corner) + return Cursor.E_RESIZE_CURSOR; + + return Cursor.DEFAULT_CURSOR; + } + } + + /** + * This helper class listens to the JDesktopPane that parents this + * JInternalFrame and listens for resize events and resizes the + * JInternalFrame appropriately. + */ + protected class ComponentHandler implements ComponentListener + { + /** + * This method is called when the JDesktopPane is hidden. + * + * @param e + * The ComponentEvent fired. + */ + public void componentHidden(ComponentEvent e) + { + // Do nothing. + } + + /** + * This method is called when the JDesktopPane is moved. + * + * @param e + * The ComponentEvent fired. + */ + public void componentMoved(ComponentEvent e) + { + // Do nothing. + } + + /** + * This method is called when the JDesktopPane is resized. + * + * @param e + * The ComponentEvent fired. + */ + public void componentResized(ComponentEvent e) + { + if (frame.isMaximum()) + { + Container parent = frame.getParent(); + Insets i = parent.getInsets(); + int width = parent.getWidth() - i.left - i.right; + int height = parent.getHeight() - i.top - i.bottom; + frame.setBounds(0, 0, width, height); + } + } + + /** + * This method is called when the JDesktopPane is shown. + * + * @param e + * The ComponentEvent fired. + */ + public void componentShown(ComponentEvent e) + { + // Do nothing. + } + } + + /** + * This helper class acts as the LayoutManager for JInternalFrames. + */ + public class InternalFrameLayout implements LayoutManager + { + /** + * This method is called when the given Component is added to the + * JInternalFrame. + * + * @param name + * The name of the Component. + * @param c + * The Component added. + */ + public void addLayoutComponent(String name, Component c) + { + // Nothing to do here. + } + + /** + * This method is used to set the bounds of the children of the + * JInternalFrame. + * + * @param c + * The Container to lay out. + */ + public void layoutContainer(Container c) + { + Dimension dims = frame.getSize(); + Insets insets = frame.getInsets(); + + dims.width -= insets.left + insets.right; + dims.height -= insets.top + insets.bottom; + + int nh = 0; + int sh = 0; + int ew = 0; + int ww = 0; + + if (northPane != null) + { + Dimension nDims = northPane.getPreferredSize(); + nh = Math.min(nDims.height, dims.height); + + northPane.setBounds(insets.left, insets.top, dims.width, nh); + } + + if (southPane != null) + { + Dimension sDims = southPane.getPreferredSize(); + sh = Math.min(sDims.height, dims.height - nh); + + southPane.setBounds(insets.left, insets.top + dims.height - sh, + dims.width, sh); + } + + int remHeight = dims.height - sh - nh; + + if (westPane != null) + { + Dimension wDims = westPane.getPreferredSize(); + ww = Math.min(dims.width, wDims.width); + + westPane.setBounds(insets.left, insets.top + nh, ww, remHeight); + } + + if (eastPane != null) + { + Dimension eDims = eastPane.getPreferredSize(); + ew = Math.min(eDims.width, dims.width - ww); + + eastPane.setBounds(insets.left + dims.width - ew, insets.top + nh, + ew, remHeight); + } + + int remWidth = dims.width - ww - ew; + + frame.getRootPane().setBounds(insets.left + ww, insets.top + nh, + remWidth, remHeight); + } + + /** + * This method returns the minimum layout size. + * + * @param c + * The Container to find a minimum layout size for. + * @return The minimum dimensions for the JInternalFrame. + */ + public Dimension minimumLayoutSize(Container c) + { + return getSize(c, true); + } + + /** + * Th8is method returns the preferred layout size. + * + * @param c + * The Container to find a preferred layout size for. + * @return The preferred dimensions for the JInternalFrame. + */ + public Dimension preferredLayoutSize(Container c) + { + return getSize(c, false); + } + + /** + * DOCUMENT ME! + * + * @param c + * DOCUMENT ME! + * @param min + * DOCUMENT ME! + * @return DOCUMENT ME! + */ + private Dimension getSize(Container c, boolean min) + { + Insets insets = frame.getInsets(); + + Dimension contentDims = frame.getContentPane().getPreferredSize(); + if (min) + contentDims.width = contentDims.height = 0; + int nWidth = 0; + int nHeight = 0; + int sWidth = 0; + int sHeight = 0; + int eWidth = 0; + int eHeight = 0; + int wWidth = 0; + int wHeight = 0; + Dimension dims; + + if (northPane != null) + { + dims = northPane.getPreferredSize(); + if (dims != null) + { + nWidth = dims.width; + nHeight = dims.height; + } + } + + if (southPane != null) + { + dims = southPane.getPreferredSize(); + if (dims != null) + { + sWidth = dims.width; + sHeight = dims.height; + } + } + + if (eastPane != null) + { + dims = eastPane.getPreferredSize(); + if (dims != null) + { + sWidth = dims.width; + sHeight = dims.height; + } + } + + if (westPane != null) + { + dims = westPane.getPreferredSize(); + if (dims != null) + { + wWidth = dims.width; + wHeight = dims.height; + } + } + + int width = Math.max(sWidth, nWidth); + width = Math.max(width, contentDims.width + eWidth + wWidth); + + int height = Math.max(eHeight, wHeight); + height = Math.max(height, contentDims.height); + height += nHeight + sHeight; + + width += insets.left + insets.right; + height += insets.top + insets.bottom; + + return new Dimension(width, height); + } + + /** + * This method is called when a Component is removed from the + * JInternalFrame. + * + * @param c The Component that was removed. + */ + public void removeLayoutComponent(Component c) + { + // Nothing to do here. + } + } + + /** + * This helper class is used to listen to the JDesktopPane's glassPane for + * MouseEvents. The JInternalFrame can then be selected if a click is + * detected on its children. + */ + protected class GlassPaneDispatcher implements MouseInputListener + { + /** The MouseEvent target. */ + private transient Component mouseEventTarget; + + private Component dragTarget; + + /** + * Indicates if we are currently in a dragging operation or not. + */ + private boolean isDragging; + + /** + * This method is called when the mouse enters the glass pane. + * + * @param e + * The MouseEvent. + */ + public void mouseEntered(MouseEvent e) + { + handleEvent(e); + } + + /** + * This method is called when the mouse is clicked on the glass pane. + * + * @param e + * The MouseEvent. + */ + public void mouseClicked(MouseEvent e) + { + handleEvent(e); + } + + /** + * This method is called when the mouse is dragged in the glass pane. + * + * @param e + * The MouseEvent. + */ + public void mouseDragged(MouseEvent e) + { + handleEvent(e); + } + + /** + * This method is called when the mouse exits the glass pane. + * + * @param e + * The MouseEvent. + */ + public void mouseExited(MouseEvent e) + { + handleEvent(e); + } + + /** + * This method is called when the mouse is moved in the glass pane. + * + * @param e + * The MouseEvent. + */ + public void mouseMoved(MouseEvent e) + { + handleEvent(e); + } + + /** + * This method is called when the mouse is pressed in the glass pane. + * + * @param e + * The MouseEvent. + */ + public void mousePressed(MouseEvent e) + { + // Experiments show that this seems to call the + // borderListener.mousePressed() method to activate the frame. + if (borderListener != null) + borderListener.mousePressed(e); + handleEvent(e); + } + + /** + * This method is called when the mouse is released in the glass pane. + * + * @param e + * The MouseEvent. + */ + public void mouseReleased(MouseEvent e) + { + handleEvent(e); + } + + /** + * This is a helper method that dispatches the GlassPane MouseEvents to the + * proper component. + * + * @param e the mouse event to be dispatched + */ + private void handleEvent(MouseEvent e) + { + // Find candidate component inside the JInternalFrame. + Component target = frame.getLayeredPane().findComponentAt(e.getX(), + e.getY()); + + // Now search upwards to find a component that actually has + // a MouseListener attached. + while (target != null + && target.getMouseListeners().length == 0 + && target.getMouseMotionListeners().length == 0 + && target.getMouseWheelListeners().length == 0) + { + target = target.getParent(); + } + + if (target != null) + { + int id = e.getID(); + switch (id) + { + case MouseEvent.MOUSE_ENTERED: + // Now redispatch the thing. + if (! isDragging || frame.isSelected()) + { + mouseEventTarget = target; + redispatch(id, e, mouseEventTarget); + } + break; + case MouseEvent.MOUSE_EXITED: + if (! isDragging || frame.isSelected()) + { + redispatch(id, e, mouseEventTarget); + } + break; + case MouseEvent.MOUSE_PRESSED: + mouseEventTarget = target; + redispatch(id, e, mouseEventTarget); + // Start dragging. + dragTarget = target; + break; + case MouseEvent.MOUSE_RELEASED: + if (isDragging) + { + redispatch(id, e, dragTarget); + isDragging = false; + } + else + redispatch(id, e, mouseEventTarget); + break; + case MouseEvent.MOUSE_CLICKED: + redispatch(id, e, mouseEventTarget); + break; + case MouseEvent.MOUSE_MOVED: + if (target != mouseEventTarget) + { + // Create additional MOUSE_EXITED/MOUSE_ENTERED pairs. + redispatch(MouseEvent.MOUSE_EXITED, e, mouseEventTarget); + mouseEventTarget = target; + redispatch(MouseEvent.MOUSE_ENTERED, e, mouseEventTarget); + } + redispatch(id, e, mouseEventTarget); + break; + case MouseEvent.MOUSE_DRAGGED: + if (! isDragging) + isDragging = true; + redispatch(id, e, mouseEventTarget); + break; + case MouseEvent.MOUSE_WHEEL: + redispatch(id, e, mouseEventTarget); + break; + default: + assert false : "Must not reach here"; + } + } + } + + /** + * Redispatches the event to the real target with the specified id. + * + * @param id the new event ID + * @param e the original event + * @param target the real event target + */ + private void redispatch(int id, MouseEvent e, Component target) + { + Point p = SwingUtilities.convertPoint(frame.getLayeredPane(), e.getX(), + e.getY(), target); + MouseEvent ev = new MouseEvent(target, id, e.getWhen(), + e.getModifiers() | e.getModifiersEx(), + p.x, p.y, e.getClickCount(), + e.isPopupTrigger()); + target.dispatchEvent(ev); + } + } + + /** + * This helper class listens for PropertyChangeEvents from the + * JInternalFrame. + */ + public class InternalFramePropertyChangeListener + implements PropertyChangeListener + { + + /** + * This method is called when one of the JInternalFrame's properties change. + * + * @param evt + * The PropertyChangeEvent. + */ + public void propertyChange(PropertyChangeEvent evt) + { + String property = evt.getPropertyName(); + if (property.equals(JInternalFrame.IS_MAXIMUM_PROPERTY)) + { + if (frame.isMaximum()) + maximizeFrame(frame); + else + minimizeFrame(frame); + } + else if (property.equals(JInternalFrame.IS_ICON_PROPERTY)) + { + if (frame.isIcon()) + iconifyFrame(frame); + else + deiconifyFrame(frame); + } + else if (property.equals(JInternalFrame.IS_SELECTED_PROPERTY)) + { + Component glassPane = frame.getGlassPane(); + if (frame.isSelected()) + { + activateFrame(frame); + glassPane.setVisible(false); + } + else + { + deactivateFrame(frame); + glassPane.setVisible(true); + } + } + else if (property.equals(JInternalFrame.ROOT_PANE_PROPERTY) + || property.equals(JInternalFrame.GLASS_PANE_PROPERTY)) + { + Component old = (Component) evt.getOldValue(); + if (old != null) + { + old.removeMouseListener(glassPaneDispatcher); + old.removeMouseMotionListener(glassPaneDispatcher); + } + + Component newPane = (Component) evt.getNewValue(); + if (newPane != null) + { + newPane.addMouseListener(glassPaneDispatcher); + newPane.addMouseMotionListener(glassPaneDispatcher); + } + + frame.revalidate(); + } + else if (property.equals(JInternalFrame.IS_CLOSED_PROPERTY)) + { + if (evt.getNewValue() == Boolean.TRUE) + { + Container parent = frame.getParent(); + if (parent != null) + parent.removeComponentListener(componentListener); + closeFrame(frame); + } + } + else if (property.equals("ancestor")) + { + Container newParent = (Container) evt.getNewValue(); + Container oldParent = (Container) evt.getOldValue(); + if (newParent != null) + { + newParent.addComponentListener(componentListener); + } + else if (oldParent != null) + { + oldParent.removeComponentListener(componentListener); + } + } + } + } + + /** + * This helper class is the border for the JInternalFrame. + */ + class InternalFrameBorder extends AbstractBorder implements + UIResource + { + /** + * The width of the border. + */ + static final int bSize = 5; + + /** + * The size of the corners (also used by the mouse listener). + */ + static final int cornerSize = 10; + + /** + * This method returns whether the border is opaque. + * + * @return Whether the border is opaque. + */ + public boolean isBorderOpaque() + { + return true; + } + + /** + * This method returns the insets of the border. + * + * @param c + * The Component to find border insets for. + * @return The border insets. + */ + public Insets getBorderInsets(Component c) + { + return new Insets(bSize, bSize, bSize, bSize); + } + + /** + * This method paints the border. + * + * @param c + * The Component that owns the border. + * @param g + * The Graphics object to paint with. + * @param x + * The x coordinate to paint at. + * @param y + * The y coordinate to paint at. + * @param width + * The width of the Component. + * @param height + * The height of the Component. + */ + public void paintBorder(Component c, Graphics g, int x, int y, int width, + int height) + { + g.translate(x, y); + Color saved = g.getColor(); + Rectangle b = frame.getBounds(); + + Color d = c.getBackground(); + g.setColor(d); + g.fillRect(0, 0, bSize, b.height); + g.fillRect(0, 0, b.width, bSize); + g.fillRect(0, b.height - bSize, b.width, bSize); + g.fillRect(b.width - bSize, 0, bSize, b.height); + + int x1 = 0; + int x2 = bSize; + int x3 = b.width - bSize; + int x4 = b.width; + + int y1 = 0; + int y2 = bSize; + int y3 = b.height - bSize; + int y4 = b.height; + + g.setColor(Color.GRAY); + g.fillRect(0, 0, bSize, y4); + g.fillRect(0, 0, x4, bSize); + g.fillRect(0, y3, b.width, bSize); + g.fillRect(x3, 0, bSize, b.height); + + g.fill3DRect(0, cornerSize, bSize, b.height - 2 * cornerSize, false); + g.fill3DRect(cornerSize, 0, b.width - 2 * cornerSize, bSize, false); + g.fill3DRect(cornerSize, b.height - bSize, b.width - 2 * cornerSize, + bSize, false); + g.fill3DRect(b.width - bSize, cornerSize, bSize, + b.height - 2 * cornerSize, false); + + g.translate(-x, -y); + g.setColor(saved); + } + } + + /** + * This action triggers the system menu. + * + * @author Roman Kennke (kennke@aicas.com) + */ + private class ShowSystemMenuAction + extends AbstractAction + { + public void actionPerformed(ActionEvent e) + { + if (titlePane != null) + { + titlePane.showSystemMenu(); + } + } + } + + /** + * The MouseListener that is responsible for dragging and resizing the + * JInternalFrame in response to MouseEvents. + */ + protected MouseInputAdapter borderListener; + + /** + * The ComponentListener that is responsible for resizing the JInternalFrame + * in response to ComponentEvents from the JDesktopPane. + */ + protected ComponentListener componentListener; + + /** + * The MouseListener that is responsible for activating the JInternalFrame + * when the mouse press activates one of its descendents. + */ + protected MouseInputListener glassPaneDispatcher; + + /** + * The PropertyChangeListener that is responsible for listening to + * PropertyChangeEvents from the JInternalFrame. + */ + protected PropertyChangeListener propertyChangeListener; + + /** The InternalFrameListener that listens to the JInternalFrame. */ + private transient BasicInternalFrameListener internalFrameListener; + + /** The JComponent placed at the east region of the JInternalFrame. */ + protected JComponent eastPane; + + /** The JComponent placed at the north region of the JInternalFrame. */ + protected JComponent northPane; + + /** The JComponent placed at the south region of the JInternalFrame. */ + protected JComponent southPane; + + /** The JComponent placed at the west region of the JInternalFrame. */ + protected JComponent westPane; + + /** + * The Keystroke bound to open the menu. + * @deprecated + */ + protected KeyStroke openMenuKey; + + /** The TitlePane displayed at the top of the JInternalFrame. */ + protected BasicInternalFrameTitlePane titlePane; + + /** The JInternalFrame this UI is responsible for. */ + protected JInternalFrame frame; + + /** The LayoutManager used in the JInternalFrame. */ + protected LayoutManager internalFrameLayout; + + /** The JDesktopPane that is the parent of the JInternalFrame. */ + private transient JDesktopPane desktopPane; + + /** + * Creates a new BasicInternalFrameUI object. + * + * @param b The JInternalFrame this UI will represent. + */ + public BasicInternalFrameUI(JInternalFrame b) + { + // Nothing to do here. + } + + /** + * This method will create a new BasicInternalFrameUI for the given + * JComponent. + * + * @param b The JComponent to create a BasicInternalFrameUI for. + * + * @return A new BasicInternalFrameUI. + */ + public static ComponentUI createUI(JComponent b) + { + return new BasicInternalFrameUI((JInternalFrame) b); + } + + /** + * This method installs a UI for the JInternalFrame. + * + * @param c The JComponent to install this UI on. + */ + public void installUI(JComponent c) + { + if (c instanceof JInternalFrame) + { + frame = (JInternalFrame) c; + + installDefaults(); + installListeners(); + installComponents(); + installKeyboardActions(); + + if (! frame.isSelected()) + frame.getGlassPane().setVisible(true); + } + } + + /** + * This method reverses the work done by installUI. + * + * @param c The JComponent to uninstall this UI for. + */ + public void uninstallUI(JComponent c) + { + uninstallKeyboardActions(); + uninstallComponents(); + uninstallListeners(); + uninstallDefaults(); + + frame.getRootPane().getGlassPane().setVisible(false); + frame = null; + } + + /** + * This method installs the defaults specified by the look and feel. + */ + protected void installDefaults() + { + internalFrameLayout = createLayoutManager(); + frame.setLayout(internalFrameLayout); + LookAndFeel.installBorder(frame, "InternalFrame.border"); + frame.setFrameIcon(UIManager.getIcon("InternalFrame.icon")); + + // Let the content pane inherit the background color from its + // frame by setting the background to null. + Component contentPane = frame.getContentPane(); + if (contentPane != null + && contentPane.getBackground() instanceof UIResource) + { + contentPane.setBackground(null); + } + } + + /** + * This method installs the keyboard actions for the JInternalFrame. + */ + protected void installKeyboardActions() + { + ActionMapUIResource am = new ActionMapUIResource(); + am.put("showSystemMenu", new ShowSystemMenuAction()); + + // The RI impl installs the audio actions as parent of the UI action map, + // so do we. + BasicLookAndFeel blaf = (BasicLookAndFeel) UIManager.getLookAndFeel(); + ActionMap audioActionMap = blaf.getAudioActionMap(); + am.setParent(audioActionMap); + + SwingUtilities.replaceUIActionMap(frame, am); + } + + /** + * This method installs the Components for the JInternalFrame. + */ + protected void installComponents() + { + setNorthPane(createNorthPane(frame)); + setSouthPane(createSouthPane(frame)); + setEastPane(createEastPane(frame)); + setWestPane(createWestPane(frame)); + } + + /** + * This method installs the listeners for the JInternalFrame. + */ + protected void installListeners() + { + glassPaneDispatcher = createGlassPaneDispatcher(); + createInternalFrameListener(); + borderListener = createBorderListener(frame); + componentListener = createComponentListener(); + propertyChangeListener = createPropertyChangeListener(); + + frame.addMouseListener(borderListener); + frame.addMouseMotionListener(borderListener); + frame.addInternalFrameListener(internalFrameListener); + frame.addPropertyChangeListener(propertyChangeListener); + frame.getRootPane().getGlassPane().addMouseListener(glassPaneDispatcher); + frame.getRootPane().getGlassPane().addMouseMotionListener(glassPaneDispatcher); + + Container parent = frame.getParent(); + if (parent != null) + { + parent.addComponentListener(componentListener); + } + } + + /** + * This method uninstalls the defaults for the JInternalFrame. + */ + protected void uninstallDefaults() + { + frame.setBorder(null); + frame.setLayout(null); + internalFrameLayout = null; + } + + /** + * This method uninstalls the Components for the JInternalFrame. + */ + protected void uninstallComponents() + { + setNorthPane(null); + setSouthPane(null); + setEastPane(null); + setWestPane(null); + } + + /** + * This method uninstalls the listeners for the JInternalFrame. + */ + protected void uninstallListeners() + { + + Container parent = frame.getParent(); + if (parent != null) + { + parent.removeComponentListener(componentListener); + } + componentListener = null; + + frame.getRootPane().getGlassPane().removeMouseMotionListener(glassPaneDispatcher); + frame.getRootPane().getGlassPane().removeMouseListener(glassPaneDispatcher); + + frame.removePropertyChangeListener(propertyChangeListener); + frame.removeInternalFrameListener(internalFrameListener); + frame.removeMouseMotionListener(borderListener); + frame.removeMouseListener(borderListener); + + propertyChangeListener = null; + + borderListener = null; + internalFrameListener = null; + glassPaneDispatcher = null; + } + + /** + * This method uninstalls the keyboard actions for the JInternalFrame. + */ + protected void uninstallKeyboardActions() + { + SwingUtilities.replaceUIActionMap(frame, null); + SwingUtilities.replaceUIInputMap(frame, JComponent.WHEN_IN_FOCUSED_WINDOW, + null); + } + + /** + * This method creates a new LayoutManager for the JInternalFrame. + * + * @return A new LayoutManager for the JInternalFrame. + */ + protected LayoutManager createLayoutManager() + { + return new InternalFrameLayout(); + } + + /** + * This method creates a new PropertyChangeListener for the JInternalFrame. + * + * @return A new PropertyChangeListener for the JInternalFrame. + */ + protected PropertyChangeListener createPropertyChangeListener() + { + return new InternalFramePropertyChangeListener(); + } + + /** + * This method returns the preferred size of the given JComponent. + * + * @param x The JComponent to find a preferred size for. + * + * @return The preferred size. + */ + public Dimension getPreferredSize(JComponent x) + { + Dimension pref = null; + LayoutManager layout = frame.getLayout(); + if (frame == x && layout != null) + pref = layout.preferredLayoutSize(frame); + else + pref = new Dimension(100, 100); + return pref; + } + + /** + * This method returns the minimum size of the given JComponent. + * + * @param x The JComponent to find a minimum size for. + * + * @return The minimum size. + */ + public Dimension getMinimumSize(JComponent x) + { + Dimension min = null; + LayoutManager layout = frame.getLayout(); + if (frame == x && layout != null) + min = layout.minimumLayoutSize(frame); + else + min = new Dimension(0, 0); + return min; + } + + /** + * This method returns the maximum size of the given JComponent. + * + * @param x The JComponent to find a maximum size for. + * + * @return The maximum size. + */ + public Dimension getMaximumSize(JComponent x) + { + Dimension max = null; + LayoutManager layout = frame.getLayout(); + if (frame == x && layout != null && layout instanceof LayoutManager2) + max = ((LayoutManager2) layout).maximumLayoutSize(frame); + else + max = new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE); + return max; + } + + /** + * This method replaces the currentPane with the newPane. When replacing it + * also removes the MouseHandlers for the old pane and installs them on + * the new pane. + * + * @param currentPane The old pane to remove. + * @param newPane The new pane to install. + */ + protected void replacePane(JComponent currentPane, JComponent newPane) + { + if (currentPane != null) + { + deinstallMouseHandlers(currentPane); + frame.remove(currentPane); + } + + if (newPane != null) + { + installMouseHandlers(newPane); + frame.add(newPane); + } + } + + /** + * This method removes the necessary MouseListeners from the given + * JComponent. + * + * @param c The JComponent to remove MouseListeners from. + */ + protected void deinstallMouseHandlers(JComponent c) + { + c.removeMouseListener(borderListener); + c.removeMouseMotionListener(borderListener); + } + + /** + * This method installs the necessary MouseListeners from the given + * JComponent. + * + * @param c The JComponent to install MouseListeners on. + */ + protected void installMouseHandlers(JComponent c) + { + c.addMouseListener(borderListener); + c.addMouseMotionListener(borderListener); + } + + /** + * This method creates the north pane used in the JInternalFrame. + * + * @param w The JInternalFrame to create a north pane for. + * + * @return The north pane. + */ + protected JComponent createNorthPane(JInternalFrame w) + { + titlePane = new BasicInternalFrameTitlePane(w); + return titlePane; + } + + /** + * This method creates the west pane used in the JInternalFrame. + * + * @param w The JInternalFrame to create a west pane for. + * + * @return The west pane. + */ + protected JComponent createWestPane(JInternalFrame w) + { + return null; + } + + /** + * This method creates the south pane used in the JInternalFrame. + * + * @param w The JInternalFrame to create a south pane for. + * + * @return The south pane. + */ + protected JComponent createSouthPane(JInternalFrame w) + { + return null; + } + + /** + * This method creates the east pane used in the JInternalFrame. + * + * @param w The JInternalFrame to create an east pane for. + * + * @return The east pane. + */ + protected JComponent createEastPane(JInternalFrame w) + { + return null; + } + + /** + * This method returns a new BorderListener for the given JInternalFrame. + * + * @param w The JIntenalFrame to create a BorderListener for. + * + * @return A new BorderListener. + */ + protected MouseInputAdapter createBorderListener(JInternalFrame w) + { + return new BorderListener(); + } + + /** + * This method creates a new InternalFrameListener for the JInternalFrame. + */ + protected void createInternalFrameListener() + { + internalFrameListener = new BasicInternalFrameListener(); + } + + /** + * DOCUMENT ME! + * + * @return DOCUMENT ME! + */ + protected final boolean isKeyBindingRegistered() + { + // FIXME: Implement. + return false; + } + + /** + * DOCUMENT ME! + * + * @param b DOCUMENT ME! + */ + protected final void setKeyBindingRegistered(boolean b) + { + // FIXME: Implement. + } + + /** + * DOCUMENT ME! + * + * @return DOCUMENT ME! + */ + public final boolean isKeyBindingActive() + { + // FIXME: Implement. + return false; + } + + /** + * DOCUMENT ME! + * + * @param b DOCUMENT ME! + */ + protected final void setKeyBindingActive(boolean b) + { + // FIXME: Implement. + } + + /** + * DOCUMENT ME! + */ + protected void setupMenuOpenKey() + { + // FIXME: Implement. + } + + /** + * DOCUMENT ME! + */ + protected void setupMenuCloseKey() + { + // FIXME: Implement. + } + + /** + * This method returns the north pane. + * + * @return The north pane. + */ + public JComponent getNorthPane() + { + return northPane; + } + + /** + * This method sets the north pane to be the given JComponent. + * + * @param c The new north pane. + */ + public void setNorthPane(JComponent c) + { + replacePane(northPane, c); + northPane = c; + // the following is needed to make internal frames draggable when using + // the JGoodies PlasticLookAndFeel, because it overrides the + // createNorthPane() method and doesn't assign anything to the titlePane + // field. It is possible there is another way to make this work, but + // I didn't find it... + if (c instanceof BasicInternalFrameTitlePane) + titlePane = (BasicInternalFrameTitlePane) c; + } + + /** + * This method returns the south pane. + * + * @return The south pane. + */ + public JComponent getSouthPane() + { + return southPane; + } + + /** + * This method sets the south pane to be the given JComponent. + * + * @param c The new south pane. + */ + public void setSouthPane(JComponent c) + { + replacePane(southPane, c); + southPane = c; + } + + /** + * This method sets the east pane to be the given JComponent. + * + * @param c The new east pane. + */ + public void setEastPane(JComponent c) + { + replacePane(eastPane, c); + eastPane = c; + } + + /** + * This method returns the east pane. + * + * @return The east pane. + */ + public JComponent getEastPane() + { + return eastPane; + } + + /** + * This method sets the west pane to be the given JComponent. + * + * @param c The new west pane. + */ + public void setWestPane(JComponent c) + { + replacePane(westPane, c); + westPane = c; + } + + /** + * This method returns the west pane. + * + * @return The west pane. + */ + public JComponent getWestPane() + { + return westPane; + } + + /** + * This method returns the DesktopManager to use with the JInternalFrame. + * + * @return The DesktopManager to use with the JInternalFrame. + */ + protected DesktopManager getDesktopManager() + { + DesktopManager value = null; + JDesktopPane pane = frame.getDesktopPane(); + if (pane != null) + value = frame.getDesktopPane().getDesktopManager(); + if (value == null) + value = createDesktopManager(); + return value; + } + + /** + * This method returns a default DesktopManager that can be used with this + * JInternalFrame. + * + * @return A default DesktopManager that can be used with this + * JInternalFrame. + */ + protected DesktopManager createDesktopManager() + { + return new DefaultDesktopManager(); + } + + /** + * This is a convenience method that closes the JInternalFrame. + * + * @param f The JInternalFrame to close. + */ + protected void closeFrame(JInternalFrame f) + { + getDesktopManager().closeFrame(f); + } + + /** + * This is a convenience method that maximizes the JInternalFrame. + * + * @param f The JInternalFrame to maximize. + */ + protected void maximizeFrame(JInternalFrame f) + { + getDesktopManager().maximizeFrame(f); + } + + /** + * This is a convenience method that minimizes the JInternalFrame. + * + * @param f The JInternalFrame to minimize. + */ + protected void minimizeFrame(JInternalFrame f) + { + getDesktopManager().minimizeFrame(f); + } + + /** + * This is a convenience method that iconifies the JInternalFrame. + * + * @param f The JInternalFrame to iconify. + */ + protected void iconifyFrame(JInternalFrame f) + { + getDesktopManager().iconifyFrame(f); + } + + /** + * This is a convenience method that deiconifies the JInternalFrame. + * + * @param f The JInternalFrame to deiconify. + */ + protected void deiconifyFrame(JInternalFrame f) + { + getDesktopManager().deiconifyFrame(f); + } + + /** + * This is a convenience method that activates the JInternalFrame. + * + * @param f The JInternalFrame to activate. + */ + protected void activateFrame(JInternalFrame f) + { + getDesktopManager().activateFrame(f); + } + + /** + * This is a convenience method that deactivates the JInternalFrame. + * + * @param f the JInternalFrame to deactivate + */ + protected void deactivateFrame(JInternalFrame f) + { + getDesktopManager().deactivateFrame(f); + } + + /** + * This method returns a new ComponentListener for the JDesktopPane. + * + * @return A new ComponentListener. + */ + protected ComponentListener createComponentListener() + { + return new ComponentHandler(); + } + + /** + * This method returns a new GlassPaneDispatcher. + * + * @return A new GlassPaneDispatcher. + */ + protected MouseInputListener createGlassPaneDispatcher() + { + return new GlassPaneDispatcher(); + } +} diff --git a/libjava/classpath/javax/swing/plaf/basic/BasicLabelUI.java b/libjava/classpath/javax/swing/plaf/basic/BasicLabelUI.java new file mode 100644 index 000000000..9469b5695 --- /dev/null +++ b/libjava/classpath/javax/swing/plaf/basic/BasicLabelUI.java @@ -0,0 +1,542 @@ +/* BasicLabelUI.java + Copyright (C) 2002, 2004, 2006, 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 java.awt.Component; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.FontMetrics; +import java.awt.Graphics; +import java.awt.Insets; +import java.awt.Rectangle; +import java.awt.Toolkit; +import java.awt.event.ActionEvent; +import java.awt.event.KeyEvent; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; + +import javax.swing.AbstractAction; +import javax.swing.ActionMap; +import javax.swing.Icon; +import javax.swing.InputMap; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.KeyStroke; +import javax.swing.LookAndFeel; +import javax.swing.SwingUtilities; +import javax.swing.plaf.ComponentUI; +import javax.swing.plaf.LabelUI; +import javax.swing.text.View; + +/** + * This is the Basic Look and Feel class for the JLabel. One BasicLabelUI + * object is used to paint all JLabels that utilize the Basic Look and Feel. + */ +public class BasicLabelUI extends LabelUI implements PropertyChangeListener +{ + /** The labelUI that is shared by all labels. */ + protected static BasicLabelUI labelUI; + + /** + * These fields hold the rectangles for the whole label, + * the icon and the text. + */ + private Rectangle vr; + private Rectangle ir; + private Rectangle tr; + + /** + * A cached Insets object for reuse in the label layout methods. + */ + private Insets cachedInsets; + + /** + * Creates a new BasicLabelUI object. + */ + public BasicLabelUI() + { + super(); + vr = new Rectangle(); + ir = new Rectangle(); + tr = new Rectangle(); + } + + /** + * Creates and returns a UI for the label. Since one UI is shared by all + * labels, this means creating only if necessary and returning the shared + * UI. + * + * @param c The {@link JComponent} that a UI is being created for. + * + * @return A label UI for the Basic Look and Feel. + */ + public static ComponentUI createUI(JComponent c) + { + if (labelUI == null) + labelUI = new BasicLabelUI(); + return labelUI; + } + + /** + * Returns the preferred size of this component as calculated by the + * {@link #layoutCL(JLabel, FontMetrics, String, Icon, Rectangle, Rectangle, + * Rectangle)} method. + * + * @param c This {@link JComponent} to get a preferred size for. + * + * @return The preferred size. + */ + public Dimension getPreferredSize(JComponent c) + { + JLabel lab = (JLabel) c; + Insets insets = lab.getInsets(); + int insetsX = insets.left + insets.right; + int insetsY = insets.top + insets.bottom; + Icon icon = lab.getIcon(); + String text = lab.getText(); + Dimension ret; + if (icon == null && text == null) + ret = new Dimension(insetsX, insetsY); + else if (icon != null && text == null) + ret = new Dimension(icon.getIconWidth() + insetsX, + icon.getIconHeight() + insetsY); + else + { + FontMetrics fm = getFontMetrics(lab); + ir.x = 0; + ir.y = 0; + ir.width = 0; + ir.height = 0; + tr.x = 0; + tr.y = 0; + tr.width = 0; + tr.height = 0; + vr.x = 0; + vr.y = 0; + vr.width = Short.MAX_VALUE; + vr.height = Short.MAX_VALUE; + layoutCL(lab, fm, text, icon, vr, ir, tr); + Rectangle cr = SwingUtilities.computeUnion(tr.x, tr.y, tr.width, + tr.height, ir); + ret = new Dimension(cr.width + insetsX, cr.height + insetsY); + } + return ret; + } + + /** + * This method returns the minimum size of the {@link JComponent} given. If + * this method returns null, then it is up to the Layout Manager to give + * this component a minimum size. + * + * @param c The {@link JComponent} to get a minimum size for. + * + * @return The minimum size. + */ + public Dimension getMinimumSize(JComponent c) + { + return getPreferredSize(c); + } + + /** + * This method returns the maximum size of the {@link JComponent} given. If + * this method returns null, then it is up to the Layout Manager to give + * this component a maximum size. + * + * @param c The {@link JComponent} to get a maximum size for. + * + * @return The maximum size. + */ + public Dimension getMaximumSize(JComponent c) + { + return getPreferredSize(c); + } + + /** + * The method that paints the label according to its current state. + * + * @param g The {@link Graphics} object to paint with. + * @param c The {@link JComponent} to paint. + */ + public void paint(Graphics g, JComponent c) + { + JLabel b = (JLabel) c; + Icon icon = (b.isEnabled()) ? b.getIcon() : b.getDisabledIcon(); + String text = b.getText(); + if (icon != null || (text != null && ! text.equals(""))) + { + FontMetrics fm = getFontMetrics(b); + Insets i = c.getInsets(cachedInsets); + vr.x = i.left; + vr.y = i.right; + vr.width = c.getWidth() - i.left - i.right; + vr.height = c.getHeight() - i.top - i.bottom; + ir.x = 0; + ir.y = 0; + ir.width = 0; + ir.height = 0; + tr.x = 0; + tr.y = 0; + tr.width = 0; + tr.height = 0; + + text = layoutCL(b, fm, text, icon, vr, ir, tr); + + if (icon != null) + icon.paintIcon(b, g, ir.x, ir.y); + + if (text != null && ! text.equals("")) + { + Object htmlRenderer = b.getClientProperty(BasicHTML.propertyKey); + if (htmlRenderer == null) + { + if (b.isEnabled()) + paintEnabledText(b, g, text, tr.x, tr.y + fm.getAscent()); + else + paintDisabledText(b, g, text, tr.x, tr.y + fm.getAscent()); + } + else + { + ((View) htmlRenderer).paint(g, tr); + } + } + } + } + + /** + * This method is simply calls SwingUtilities's layoutCompoundLabel. + * + * @param label The label to lay out. + * @param fontMetrics The FontMetrics for the font used. + * @param text The text to paint. + * @param icon The icon to draw. + * @param viewR The entire viewable rectangle. + * @param iconR The icon bounds rectangle. + * @param textR The text bounds rectangle. + * + * @return A possibly clipped version of the text. + */ + protected String layoutCL(JLabel label, FontMetrics fontMetrics, String text, + Icon icon, Rectangle viewR, Rectangle iconR, Rectangle textR) + { + return SwingUtilities.layoutCompoundLabel(label, fontMetrics, text, icon, + label.getVerticalAlignment(), label.getHorizontalAlignment(), label + .getVerticalTextPosition(), label.getHorizontalTextPosition(), + viewR, iconR, textR, label.getIconTextGap()); + } + + /** + * Paints the text if the label is disabled. By default, this paints the + * clipped text returned by layoutCompoundLabel using the + * background.brighter() color. It also paints the same text using the + * background.darker() color one pixel to the right and one pixel down. + * + * @param l The {@link JLabel} being painted. + * @param g The {@link Graphics} object to paint with. + * @param s The String to paint. + * @param textX The x coordinate of the start of the baseline. + * @param textY The y coordinate of the start of the baseline. + */ + protected void paintDisabledText(JLabel l, Graphics g, String s, int textX, + int textY) + { + g.setColor(l.getBackground().brighter()); + + int mnemIndex = l.getDisplayedMnemonicIndex(); + + if (mnemIndex != -1) + BasicGraphicsUtils.drawStringUnderlineCharAt(g, s, mnemIndex, textX, + textY); + else + g.drawString(s, textX, textY); + + g.setColor(l.getBackground().darker()); + if (mnemIndex != -1) + BasicGraphicsUtils.drawStringUnderlineCharAt(g, s, mnemIndex, textX + 1, + textY + 1); + else + g.drawString(s, textX + 1, textY + 1); + } + + /** + * Paints the text if the label is enabled. The text is painted using the + * foreground color. + * + * @param l The {@link JLabel} being painted. + * @param g The {@link Graphics} object to paint with. + * @param s The String to paint. + * @param textX The x coordinate of the start of the baseline. + * @param textY The y coordinate of the start of the baseline. + */ + protected void paintEnabledText(JLabel l, Graphics g, String s, int textX, + int textY) + { + g.setColor(l.getForeground()); + + int mnemIndex = l.getDisplayedMnemonicIndex(); + + if (mnemIndex != -1) + BasicGraphicsUtils.drawStringUnderlineCharAt(g, s, mnemIndex, textX, + textY); + else + g.drawString(s, textX, textY); + } + + /** + * This method installs the UI for the given {@link JComponent}. This + * method will install the component, defaults, listeners, and keyboard + * actions. + * + * @param c The {@link JComponent} that this UI is being installed on. + */ + public void installUI(JComponent c) + { + super.installUI(c); + if (c instanceof JLabel) + { + JLabel l = (JLabel) c; + + installComponents(l); + installDefaults(l); + installListeners(l); + installKeyboardActions(l); + } + } + + /** + * This method uninstalls the UI for the given {@link JComponent}. This + * method will uninstall the component, defaults, listeners, and keyboard + * actions. + * + * @param c The {@link JComponent} that this UI is being installed on. + */ + public void uninstallUI(JComponent c) + { + super.uninstallUI(c); + if (c instanceof JLabel) + { + JLabel l = (JLabel) c; + + uninstallKeyboardActions(l); + uninstallListeners(l); + uninstallDefaults(l); + uninstallComponents(l); + } + } + + /** + * This method installs the components for this {@link JLabel}. + * + * @param c The {@link JLabel} to install components for. + */ + protected void installComponents(JLabel c) + { + BasicHTML.updateRenderer(c, c.getText()); + } + + /** + * This method uninstalls the components for this {@link JLabel}. + * + * @param c The {@link JLabel} to uninstall components for. + */ + protected void uninstallComponents(JLabel c) + { + c.putClientProperty(BasicHTML.propertyKey, null); + c.putClientProperty(BasicHTML.documentBaseKey, null); + } + + /** + * This method installs the defaults that are defined in the Basic look and + * feel for this {@link JLabel}. + * + * @param c The {@link JLabel} to install defaults for. + */ + protected void installDefaults(JLabel c) + { + LookAndFeel.installColorsAndFont(c, "Label.background", "Label.foreground", + "Label.font"); + //XXX: There are properties we don't use called disabledForeground + //and disabledShadow. + } + + /** + * This method uninstalls the defaults that are defined in the Basic look + * and feel for this {@link JLabel}. + * + * @param c The {@link JLabel} to uninstall defaults for. + */ + protected void uninstallDefaults(JLabel c) + { + c.setForeground(null); + c.setBackground(null); + c.setFont(null); + } + + /** + * Installs the keyboard actions for the given {@link JLabel}. + * + * @param l The {@link JLabel} to install keyboard actions for. + */ + protected void installKeyboardActions(JLabel l) + { + Component c = l.getLabelFor(); + if (c != null) + { + int mnemonic = l.getDisplayedMnemonic(); + if (mnemonic > 0) + { + // add a keystroke for the given mnemonic mapping to 'press'; + InputMap keyMap = new InputMap(); + keyMap.put(KeyStroke.getKeyStroke(mnemonic, KeyEvent.VK_ALT), + "press"); + SwingUtilities.replaceUIInputMap(l, + JComponent.WHEN_IN_FOCUSED_WINDOW, keyMap); + + // add an action to focus the component when 'press' happens + ActionMap map = new ActionMap(); + map.put("press", new AbstractAction() { + public void actionPerformed(ActionEvent event) + { + JLabel label = (JLabel) event.getSource(); + Component c = label.getLabelFor(); + if (c != null) + c.requestFocus(); + } + }); + SwingUtilities.replaceUIActionMap(l, map); + } + } + } + + /** + * This method uninstalls the keyboard actions for the given {@link JLabel}. + * + * @param l The {@link JLabel} to uninstall keyboard actions for. + */ + protected void uninstallKeyboardActions(JLabel l) + { + SwingUtilities.replaceUIActionMap(l, null); + SwingUtilities.replaceUIInputMap(l, JComponent.WHEN_IN_FOCUSED_WINDOW, + null); + } + + /** + * This method installs the listeners for the given {@link JLabel}. The UI + * delegate only listens to the label. + * + * @param c The {@link JLabel} to install listeners for. + */ + protected void installListeners(JLabel c) + { + c.addPropertyChangeListener(this); + } + + /** + * This method uninstalls the listeners for the given {@link JLabel}. The UI + * delegate only listens to the label. + * + * @param c The {@link JLabel} to uninstall listeners for. + */ + protected void uninstallListeners(JLabel c) + { + c.removePropertyChangeListener(this); + } + + /** + * This method is called whenever any JLabel's that use this UI has one of + * their properties change. + * + * @param e The {@link PropertyChangeEvent} that describes the change. + */ + public void propertyChange(PropertyChangeEvent e) + { + if (e.getPropertyName().equals("text")) + { + String text = (String) e.getNewValue(); + JLabel l = (JLabel) e.getSource(); + BasicHTML.updateRenderer(l, text); + } + else if (e.getPropertyName().equals("displayedMnemonic")) + { + // update the key to action mapping + JLabel label = (JLabel) e.getSource(); + if (label.getLabelFor() != null) + { + int oldMnemonic = ((Integer) e.getOldValue()).intValue(); + int newMnemonic = ((Integer) e.getNewValue()).intValue(); + InputMap keyMap = label.getInputMap( + JComponent.WHEN_IN_FOCUSED_WINDOW); + keyMap.put(KeyStroke.getKeyStroke(oldMnemonic, + KeyEvent.ALT_DOWN_MASK), null); + keyMap.put(KeyStroke.getKeyStroke(newMnemonic, + KeyEvent.ALT_DOWN_MASK), "press"); + } + } + else if (e.getPropertyName().equals("labelFor")) + { + JLabel label = (JLabel) e.getSource(); + InputMap keyMap = label.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); + int mnemonic = label.getDisplayedMnemonic(); + if (mnemonic > 0) + keyMap.put(KeyStroke.getKeyStroke(mnemonic, KeyEvent.ALT_DOWN_MASK), + "press"); + } + } + + /** + * Fetches a font metrics object for the specified label. This first + * tries to get it from the label object itself by calling + * {@link Component#getFontMetrics(Font)}, and if that does not work + * (for instance, when we are in the initialization and have no parent yet), + * it asks the Toolkit for a font metrics object. + * + * @param l the label + * + * @return a suitable font metrics object + */ + private FontMetrics getFontMetrics(JLabel l) + { + Font font = l.getFont(); + FontMetrics fm = l.getFontMetrics(font); + if (fm == null) + { + Toolkit tk = Toolkit.getDefaultToolkit(); + fm = tk.getFontMetrics(font); + } + return fm; + } +} diff --git a/libjava/classpath/javax/swing/plaf/basic/BasicListUI.java b/libjava/classpath/javax/swing/plaf/basic/BasicListUI.java new file mode 100644 index 000000000..0e33957f4 --- /dev/null +++ b/libjava/classpath/javax/swing/plaf/basic/BasicListUI.java @@ -0,0 +1,1421 @@ +/* BasicListUI.java -- + Copyright (C) 2002, 2004, 2005, 2006 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 java.awt.Component; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Insets; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; +import java.awt.event.MouseEvent; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; + +import javax.swing.AbstractAction; +import javax.swing.ActionMap; +import javax.swing.CellRendererPane; +import javax.swing.DefaultListSelectionModel; +import javax.swing.InputMap; +import javax.swing.JComponent; +import javax.swing.JList; +import javax.swing.ListCellRenderer; +import javax.swing.ListModel; +import javax.swing.ListSelectionModel; +import javax.swing.LookAndFeel; +import javax.swing.SwingUtilities; +import javax.swing.TransferHandler; +import javax.swing.UIDefaults; +import javax.swing.UIManager; +import javax.swing.event.ListDataEvent; +import javax.swing.event.ListDataListener; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; +import javax.swing.event.MouseInputListener; +import javax.swing.plaf.ActionMapUIResource; +import javax.swing.plaf.ComponentUI; +import javax.swing.plaf.ListUI; +import javax.swing.plaf.UIResource; + +/** + * The Basic Look and Feel UI delegate for the + * JList. + */ +public class BasicListUI extends ListUI +{ + + /** + * A helper class which listens for {@link FocusEvent}s + * from the JList. + */ + public class FocusHandler implements FocusListener + { + /** + * Called when the JList acquires focus. + * + * @param e The FocusEvent representing focus acquisition + */ + public void focusGained(FocusEvent e) + { + repaintCellFocus(); + } + + /** + * Called when the JList loses focus. + * + * @param e The FocusEvent representing focus loss + */ + public void focusLost(FocusEvent e) + { + repaintCellFocus(); + } + + /** + * Helper method to repaint the focused cell's + * lost or acquired focus state. + */ + protected void repaintCellFocus() + { + // TODO: Implement this properly. + } + } + + /** + * A helper class which listens for {@link ListDataEvent}s generated by + * the {@link JList}'s {@link ListModel}. + * + * @see javax.swing.JList#getModel() + */ + public class ListDataHandler implements ListDataListener + { + /** + * Called when a general change has happened in the model which cannot + * be represented in terms of a simple addition or deletion. + * + * @param e The event representing the change + */ + public void contentsChanged(ListDataEvent e) + { + updateLayoutStateNeeded |= modelChanged; + list.revalidate(); + } + + /** + * Called when an interval of objects has been added to the model. + * + * @param e The event representing the addition + */ + public void intervalAdded(ListDataEvent e) + { + updateLayoutStateNeeded |= modelChanged; + list.revalidate(); + } + + /** + * Called when an inteval of objects has been removed from the model. + * + * @param e The event representing the removal + */ + public void intervalRemoved(ListDataEvent e) + { + updateLayoutStateNeeded |= modelChanged; + list.revalidate(); + } + } + + /** + * A helper class which listens for {@link ListSelectionEvent}s + * from the {@link JList}'s {@link ListSelectionModel}. + */ + public class ListSelectionHandler implements ListSelectionListener + { + /** + * Called when the list selection changes. + * + * @param e The event representing the change + */ + public void valueChanged(ListSelectionEvent e) + { + int index1 = e.getFirstIndex(); + int index2 = e.getLastIndex(); + Rectangle damaged = getCellBounds(list, index1, index2); + if (damaged != null) + list.repaint(damaged); + } + } + + /** + * This class is used to mimmic the behaviour of the JDK when registering + * keyboard actions. It is the same as the private class used in JComponent + * for the same reason. This class receives an action event and dispatches + * it to the true receiver after altering the actionCommand property of the + * event. + */ + private static class ActionListenerProxy + extends AbstractAction + { + ActionListener target; + String bindingCommandName; + + public ActionListenerProxy(ActionListener li, + String cmd) + { + target = li; + bindingCommandName = cmd; + } + + public void actionPerformed(ActionEvent e) + { + ActionEvent derivedEvent = new ActionEvent(e.getSource(), + e.getID(), + bindingCommandName, + e.getModifiers()); + target.actionPerformed(derivedEvent); + } + } + + /** + * Implements the action for the JList's keyboard commands. + */ + private class ListAction + extends AbstractAction + { + // TODO: Maybe make a couple of classes out of this bulk Action. + // Form logical groups of Actions when doing this. + + /** + * Creates a new ListAction for the specified command. + * + * @param cmd the actionCommand to set + */ + ListAction(String cmd) + { + putValue(ACTION_COMMAND_KEY, cmd); + } + + public void actionPerformed(ActionEvent e) + { + int lead = list.getLeadSelectionIndex(); + int max = list.getModel().getSize() - 1; + DefaultListSelectionModel selModel + = (DefaultListSelectionModel) list.getSelectionModel(); + String command = e.getActionCommand(); + // Do nothing if list is empty + if (max == -1) + return; + + if (command.equals("selectNextRow")) + { + selectNextIndex(); + } + else if (command.equals("selectPreviousRow")) + { + selectPreviousIndex(); + } + else if (command.equals("clearSelection")) + { + list.clearSelection(); + } + else if (command.equals("selectAll")) + { + list.setSelectionInterval(0, max); + // this next line is to restore the lead selection index to the old + // position, because select-all should not change the lead index + list.addSelectionInterval(lead, lead); + } + else if (command.equals("selectLastRow")) + { + list.setSelectedIndex(list.getModel().getSize() - 1); + } + else if (command.equals("selectLastRowChangeLead")) + { + selModel.moveLeadSelectionIndex(list.getModel().getSize() - 1); + } + else if (command.equals("scrollDownExtendSelection")) + { + int target; + if (lead == list.getLastVisibleIndex()) + { + target = Math.min(max, lead + (list.getLastVisibleIndex() + - list.getFirstVisibleIndex() + 1)); + } + else + target = list.getLastVisibleIndex(); + selModel.setLeadSelectionIndex(target); + } + else if (command.equals("scrollDownChangeLead")) + { + int target; + if (lead == list.getLastVisibleIndex()) + { + target = Math.min(max, lead + (list.getLastVisibleIndex() + - list.getFirstVisibleIndex() + 1)); + } + else + target = list.getLastVisibleIndex(); + selModel.moveLeadSelectionIndex(target); + } + else if (command.equals("scrollUpExtendSelection")) + { + int target; + if (lead == list.getFirstVisibleIndex()) + { + target = Math.max(0, lead - (list.getLastVisibleIndex() + - list.getFirstVisibleIndex() + 1)); + } + else + target = list.getFirstVisibleIndex(); + selModel.setLeadSelectionIndex(target); + } + else if (command.equals("scrollUpChangeLead")) + { + int target; + if (lead == list.getFirstVisibleIndex()) + { + target = Math.max(0, lead - (list.getLastVisibleIndex() + - list.getFirstVisibleIndex() + 1)); + } + else + target = list.getFirstVisibleIndex(); + selModel.moveLeadSelectionIndex(target); + } + else if (command.equals("selectNextRowExtendSelection")) + { + selModel.setLeadSelectionIndex(Math.min(lead + 1, max)); + } + else if (command.equals("selectFirstRow")) + { + list.setSelectedIndex(0); + } + else if (command.equals("selectFirstRowChangeLead")) + { + selModel.moveLeadSelectionIndex(0); + } + else if (command.equals("selectFirstRowExtendSelection")) + { + selModel.setLeadSelectionIndex(0); + } + else if (command.equals("selectPreviousRowExtendSelection")) + { + selModel.setLeadSelectionIndex(Math.max(0, lead - 1)); + } + else if (command.equals("scrollUp")) + { + int target; + if (lead == list.getFirstVisibleIndex()) + { + target = Math.max(0, lead - (list.getLastVisibleIndex() + - list.getFirstVisibleIndex() + 1)); + } + else + target = list.getFirstVisibleIndex(); + list.setSelectedIndex(target); + } + else if (command.equals("selectLastRowExtendSelection")) + { + selModel.setLeadSelectionIndex(list.getModel().getSize() - 1); + } + else if (command.equals("scrollDown")) + { + int target; + if (lead == list.getLastVisibleIndex()) + { + target = Math.min(max, lead + (list.getLastVisibleIndex() + - list.getFirstVisibleIndex() + 1)); + } + else + target = list.getLastVisibleIndex(); + list.setSelectedIndex(target); + } + else if (command.equals("selectNextRowChangeLead")) + { + if (selModel.getSelectionMode() != ListSelectionModel.MULTIPLE_INTERVAL_SELECTION) + selectNextIndex(); + else + { + selModel.moveLeadSelectionIndex(Math.min(max, lead + 1)); + } + } + else if (command.equals("selectPreviousRowChangeLead")) + { + if (selModel.getSelectionMode() != ListSelectionModel.MULTIPLE_INTERVAL_SELECTION) + selectPreviousIndex(); + else + { + selModel.moveLeadSelectionIndex(Math.max(0, lead - 1)); + } + } + else if (command.equals("addToSelection")) + { + list.addSelectionInterval(lead, lead); + } + else if (command.equals("extendTo")) + { + selModel.setSelectionInterval(selModel.getAnchorSelectionIndex(), + lead); + } + else if (command.equals("toggleAndAnchor")) + { + if (!list.isSelectedIndex(lead)) + list.addSelectionInterval(lead, lead); + else + list.removeSelectionInterval(lead, lead); + selModel.setAnchorSelectionIndex(lead); + } + else + { + // DEBUG: uncomment the following line to print out + // key bindings that aren't implemented yet + + // System.out.println ("not implemented: "+e.getActionCommand()); + } + + list.ensureIndexIsVisible(list.getLeadSelectionIndex()); + } + } + + /** + * A helper class which listens for {@link MouseEvent}s + * from the {@link JList}. + */ + public class MouseInputHandler implements MouseInputListener + { + /** + * Called when a mouse button press/release cycle completes + * on the {@link JList} + * + * @param event The event representing the mouse click + */ + public void mouseClicked(MouseEvent event) + { + Point click = event.getPoint(); + int index = locationToIndex(list, click); + if (index == -1) + return; + if (event.isShiftDown()) + { + if (list.getSelectionMode() == ListSelectionModel.SINGLE_SELECTION) + list.setSelectedIndex(index); + else if (list.getSelectionMode() == + ListSelectionModel.SINGLE_INTERVAL_SELECTION) + // COMPAT: the IBM VM is compatible with the following line of code. + // However, compliance with Sun's VM would correspond to replacing + // getAnchorSelectionIndex() with getLeadSelectionIndex().This is + // both unnatural and contradictory to the way they handle other + // similar UI interactions. + list.setSelectionInterval(list.getAnchorSelectionIndex(), index); + else + // COMPAT: both Sun and IBM are compatible instead with: + // list.setSelectionInterval + // (list.getLeadSelectionIndex(),index); + // Note that for IBM this is contradictory to what they did in + // the above situation for SINGLE_INTERVAL_SELECTION. + // The most natural thing to do is the following: + if (list.isSelectedIndex(list.getAnchorSelectionIndex())) + list.getSelectionModel().setLeadSelectionIndex(index); + else + list.addSelectionInterval(list.getAnchorSelectionIndex(), index); + } + else if (event.isControlDown()) + { + if (list.getSelectionMode() == ListSelectionModel.SINGLE_SELECTION) + list.setSelectedIndex(index); + else if (list.isSelectedIndex(index)) + list.removeSelectionInterval(index, index); + else + list.addSelectionInterval(index, index); + } + else + list.setSelectedIndex(index); + + list.ensureIndexIsVisible(list.getLeadSelectionIndex()); + } + + /** + * Called when a mouse button is pressed down on the + * {@link JList}. + * + * @param event The event representing the mouse press + */ + public void mousePressed(MouseEvent event) + { + // We need to explicitly request focus. + list.requestFocusInWindow(); + } + + /** + * Called when a mouse button is released on + * the {@link JList} + * + * @param event The event representing the mouse press + */ + public void mouseReleased(MouseEvent event) + { + // TODO: What should be done here, if anything? + } + + /** + * Called when the mouse pointer enters the area bounded + * by the {@link JList} + * + * @param event The event representing the mouse entry + */ + public void mouseEntered(MouseEvent event) + { + // TODO: What should be done here, if anything? + } + + /** + * Called when the mouse pointer leaves the area bounded + * by the {@link JList} + * + * @param event The event representing the mouse exit + */ + public void mouseExited(MouseEvent event) + { + // TODO: What should be done here, if anything? + } + + /** + * Called when the mouse pointer moves over the area bounded + * by the {@link JList} while a button is held down. + * + * @param event The event representing the mouse drag + */ + public void mouseDragged(MouseEvent event) + { + Point click = event.getPoint(); + int index = locationToIndex(list, click); + if (index == -1) + return; + if (!event.isShiftDown() && !event.isControlDown()) + list.setSelectedIndex(index); + + list.ensureIndexIsVisible(list.getLeadSelectionIndex()); + } + + /** + * Called when the mouse pointer moves over the area bounded + * by the {@link JList}. + * + * @param event The event representing the mouse move + */ + public void mouseMoved(MouseEvent event) + { + // TODO: What should be done here, if anything? + } + } + + /** + * Helper class which listens to {@link PropertyChangeEvent}s + * from the {@link JList}. + */ + public class PropertyChangeHandler implements PropertyChangeListener + { + /** + * Called when the {@link JList} changes one of its bound properties. + * + * @param e The event representing the property change + */ + public void propertyChange(PropertyChangeEvent e) + { + if (e.getPropertyName().equals("model")) + { + if (e.getOldValue() != null && e.getOldValue() instanceof ListModel) + { + ListModel oldModel = (ListModel) e.getOldValue(); + oldModel.removeListDataListener(listDataListener); + } + if (e.getNewValue() != null && e.getNewValue() instanceof ListModel) + { + ListModel newModel = (ListModel) e.getNewValue(); + newModel.addListDataListener(BasicListUI.this.listDataListener); + } + + updateLayoutStateNeeded |= modelChanged; + } + else if (e.getPropertyName().equals("selectionModel")) + updateLayoutStateNeeded |= selectionModelChanged; + else if (e.getPropertyName().equals("font")) + updateLayoutStateNeeded |= fontChanged; + else if (e.getPropertyName().equals("fixedCellWidth")) + updateLayoutStateNeeded |= fixedCellWidthChanged; + else if (e.getPropertyName().equals("fixedCellHeight")) + updateLayoutStateNeeded |= fixedCellHeightChanged; + else if (e.getPropertyName().equals("prototypeCellValue")) + updateLayoutStateNeeded |= prototypeCellValueChanged; + else if (e.getPropertyName().equals("cellRenderer")) + updateLayoutStateNeeded |= cellRendererChanged; + } + } + + /** + * A constant to indicate that the model has changed. + */ + protected static final int modelChanged = 1; + + /** + * A constant to indicate that the selection model has changed. + */ + protected static final int selectionModelChanged = 2; + + /** + * A constant to indicate that the font has changed. + */ + protected static final int fontChanged = 4; + + /** + * A constant to indicate that the fixedCellWidth has changed. + */ + protected static final int fixedCellWidthChanged = 8; + + /** + * A constant to indicate that the fixedCellHeight has changed. + */ + protected static final int fixedCellHeightChanged = 16; + + /** + * A constant to indicate that the prototypeCellValue has changed. + */ + protected static final int prototypeCellValueChanged = 32; + + /** + * A constant to indicate that the cellRenderer has changed. + */ + protected static final int cellRendererChanged = 64; + + /** + * Creates a new BasicListUI for the component. + * + * @param c The component to create a UI for + * + * @return A new UI + */ + public static ComponentUI createUI(final JComponent c) + { + return new BasicListUI(); + } + + /** The current focus listener. */ + protected FocusListener focusListener; + + /** The data listener listening to the model. */ + protected ListDataListener listDataListener; + + /** The selection listener listening to the selection model. */ + protected ListSelectionListener listSelectionListener; + + /** The mouse listener listening to the list. */ + protected MouseInputListener mouseInputListener; + + /** The property change listener listening to the list. */ + protected PropertyChangeListener propertyChangeListener; + + /** Saved reference to the list this UI was created for. */ + protected JList list; + + /** + * The height of a single cell in the list. This field is used when the + * fixedCellHeight property of the list is set. Otherwise this field is + * set to <code>-1</code> and {@link #cellHeights} is used instead. + */ + protected int cellHeight; + + /** The width of a single cell in the list. */ + protected int cellWidth; + + /** + * An array of varying heights of cells in the list, in cases where each + * cell might have a different height. This field is used when the + * <code>fixedCellHeight</code> property of the list is not set. Otherwise + * this field is <code>null</code> and {@link #cellHeight} is used. + */ + protected int[] cellHeights; + + /** + * A bitmask that indicates which properties of the JList have changed. + * When nonzero, indicates that the UI class is out of + * date with respect to the underlying list, and must recalculate the + * list layout before painting or performing size calculations. + * + * @see #modelChanged + * @see #selectionModelChanged + * @see #fontChanged + * @see #fixedCellWidthChanged + * @see #fixedCellHeightChanged + * @see #prototypeCellValueChanged + * @see #cellRendererChanged + */ + protected int updateLayoutStateNeeded; + + /** + * The {@link CellRendererPane} that is used for painting. + */ + protected CellRendererPane rendererPane; + + /** The action bound to KeyStrokes. */ + ListAction action; + + /** + * Calculate the height of a particular row. If there is a fixed {@link + * #cellHeight}, return it; otherwise return the specific row height + * requested from the {@link #cellHeights} array. If the requested row + * is invalid, return <code>-1</code>. + * + * @param row The row to get the height of + * + * @return The height, in pixels, of the specified row + */ + protected int getRowHeight(int row) + { + int height; + if (cellHeights == null) + height = cellHeight; + else + { + if (row < 0 || row >= cellHeights.length) + height = -1; + else + height = cellHeights[row]; + } + return height; + } + + /** + * Calculate the bounds of a particular cell, considering the upper left + * corner of the list as the origin position <code>(0,0)</code>. + * + * @param l Ignored; calculates over <code>this.list</code> + * @param index1 The first row to include in the bounds + * @param index2 The last row to incude in the bounds + * + * @return A rectangle encompassing the range of rows between + * <code>index1</code> and <code>index2</code> inclusive, or null + * such a rectangle couldn't be calculated for the given indexes. + */ + public Rectangle getCellBounds(JList l, int index1, int index2) + { + maybeUpdateLayoutState(); + + if (l != list || cellWidth == -1) + return null; + + int minIndex = Math.min(index1, index2); + int maxIndex = Math.max(index1, index2); + Point loc = indexToLocation(list, minIndex); + + // When the layoutOrientation is VERTICAL, then the width == the list + // width. Otherwise the cellWidth field is used. + int width = cellWidth; + if (l.getLayoutOrientation() == JList.VERTICAL) + width = l.getWidth(); + + Rectangle bounds = new Rectangle(loc.x, loc.y, width, + getCellHeight(minIndex)); + for (int i = minIndex + 1; i <= maxIndex; i++) + { + Point hiLoc = indexToLocation(list, i); + bounds = SwingUtilities.computeUnion(hiLoc.x, hiLoc.y, width, + getCellHeight(i), bounds); + } + + return bounds; + } + + /** + * Calculates the maximum cell height. + * + * @param index the index of the cell + * + * @return the maximum cell height + */ + private int getCellHeight(int index) + { + int height = cellHeight; + if (height <= 0) + { + if (list.getLayoutOrientation() == JList.VERTICAL) + height = getRowHeight(index); + else + { + for (int j = 0; j < cellHeights.length; j++) + height = Math.max(height, cellHeights[j]); + } + } + return height; + } + + /** + * Calculate the Y coordinate of the upper edge of a particular row, + * considering the Y coordinate <code>0</code> to occur at the top of the + * list. + * + * @param row The row to calculate the Y coordinate of + * + * @return The Y coordinate of the specified row, or <code>-1</code> if + * the specified row number is invalid + */ + protected int convertRowToY(int row) + { + int y = 0; + for (int i = 0; i < row; ++i) + { + int h = getRowHeight(i); + if (h == -1) + return -1; + y += h; + } + return y; + } + + /** + * Calculate the row number containing a particular Y coordinate, + * considering the Y coodrinate <code>0</code> to occur at the top of the + * list. + * + * @param y0 The Y coordinate to calculate the row number for + * + * @return The row number containing the specified Y value, or <code>-1</code> + * if the list model is empty + * + * @specnote This method is specified to return -1 for an invalid Y + * coordinate. However, some simple tests show that the behaviour + * is to return the index of the last list element for an Y + * coordinate that lies outside of the list bounds (even for + * negative indices). <code>-1</code> + * is only returned if the list model is empty. + */ + protected int convertYToRow(int y0) + { + if (list.getModel().getSize() == 0) + return -1; + + // When y0 < 0, then the JDK returns the maximum row index of the list. So + // do we. + if (y0 < 0) + return list.getModel().getSize() - 1; + + // Update the layout if necessary. + maybeUpdateLayoutState(); + + int index = list.getModel().getSize() - 1; + + // If a fixed cell height is set, then we can work more efficient. + if (cellHeight > 0) + index = Math.min(y0 / cellHeight, index); + // If we have no fixed cell height, we must add up each cell height up + // to y0. + else + { + int h = 0; + for (int row = 0; row < cellHeights.length; ++row) + { + h += cellHeights[row]; + if (y0 < h) + { + index = row; + break; + } + } + } + return index; + } + + /** + * Recomputes the {@link #cellHeights}, {@link #cellHeight}, and {@link + * #cellWidth} properties by examining the variouis properties of the + * {@link JList}. + */ + protected void updateLayoutState() + { + int nrows = list.getModel().getSize(); + cellHeight = -1; + cellWidth = -1; + if (cellHeights == null || cellHeights.length != nrows) + cellHeights = new int[nrows]; + ListCellRenderer rend = list.getCellRenderer(); + // Update the cellHeight(s) fields. + int fixedCellHeight = list.getFixedCellHeight(); + if (fixedCellHeight > 0) + { + cellHeight = fixedCellHeight; + cellHeights = null; + } + else + { + cellHeight = -1; + for (int i = 0; i < nrows; ++i) + { + Component flyweight = + rend.getListCellRendererComponent(list, + list.getModel().getElementAt(i), + i, list.isSelectedIndex(i), + list.getSelectionModel().getAnchorSelectionIndex() == i); + Dimension dim = flyweight.getPreferredSize(); + cellHeights[i] = dim.height; + } + } + + // Update the cellWidth field. + int fixedCellWidth = list.getFixedCellWidth(); + if (fixedCellWidth > 0) + cellWidth = fixedCellWidth; + else + { + for (int i = 0; i < nrows; ++i) + { + Component flyweight = + rend.getListCellRendererComponent(list, + list.getModel().getElementAt(i), + i, list.isSelectedIndex(i), + list.getSelectionModel().getAnchorSelectionIndex() == i); + Dimension dim = flyweight.getPreferredSize(); + cellWidth = Math.max(cellWidth, dim.width); + } + } + } + + /** + * Calls {@link #updateLayoutState} if {@link #updateLayoutStateNeeded} + * is nonzero, then resets {@link #updateLayoutStateNeeded} to zero. + */ + protected void maybeUpdateLayoutState() + { + if (updateLayoutStateNeeded != 0 || !list.isValid()) + { + updateLayoutState(); + updateLayoutStateNeeded = 0; + } + } + + /** + * Creates a new BasicListUI object. + */ + public BasicListUI() + { + updateLayoutStateNeeded = 1; + rendererPane = new CellRendererPane(); + } + + /** + * Installs various default settings (mostly colors) from the {@link + * UIDefaults} into the {@link JList} + * + * @see #uninstallDefaults + */ + protected void installDefaults() + { + LookAndFeel.installColorsAndFont(list, "List.background", + "List.foreground", "List.font"); + list.setSelectionForeground(UIManager.getColor("List.selectionForeground")); + list.setSelectionBackground(UIManager.getColor("List.selectionBackground")); + list.setOpaque(true); + } + + /** + * Resets to <code>null</code> those defaults which were installed in + * {@link #installDefaults} + */ + protected void uninstallDefaults() + { + list.setForeground(null); + list.setBackground(null); + list.setSelectionForeground(null); + list.setSelectionBackground(null); + } + + /** + * Attaches all the listeners we have in the UI class to the {@link + * JList}, its model and its selection model. + * + * @see #uninstallListeners + */ + protected void installListeners() + { + if (focusListener == null) + focusListener = createFocusListener(); + list.addFocusListener(focusListener); + if (listDataListener == null) + listDataListener = createListDataListener(); + list.getModel().addListDataListener(listDataListener); + if (listSelectionListener == null) + listSelectionListener = createListSelectionListener(); + list.addListSelectionListener(listSelectionListener); + if (mouseInputListener == null) + mouseInputListener = createMouseInputListener(); + list.addMouseListener(mouseInputListener); + list.addMouseMotionListener(mouseInputListener); + if (propertyChangeListener == null) + propertyChangeListener = createPropertyChangeListener(); + list.addPropertyChangeListener(propertyChangeListener); + } + + /** + * Detaches all the listeners we attached in {@link #installListeners}. + */ + protected void uninstallListeners() + { + list.removeFocusListener(focusListener); + list.getModel().removeListDataListener(listDataListener); + list.removeListSelectionListener(listSelectionListener); + list.removeMouseListener(mouseInputListener); + list.removeMouseMotionListener(mouseInputListener); + list.removePropertyChangeListener(propertyChangeListener); + } + + /** + * Installs keyboard actions for this UI in the {@link JList}. + */ + protected void installKeyboardActions() + { + // Install UI InputMap. + InputMap focusInputMap = (InputMap) UIManager.get("List.focusInputMap"); + SwingUtilities.replaceUIInputMap(list, JComponent.WHEN_FOCUSED, + focusInputMap); + + // Install UI ActionMap. + ActionMap am = (ActionMap) UIManager.get("List.actionMap"); + if (am == null) + { + // Create the actionMap once and store it in the current UIDefaults + // for use in other components. + am = new ActionMapUIResource(); + ListAction action; + action = new ListAction("selectPreviousRow"); + am.put("selectPreviousRow", action); + action = new ListAction("selectNextRow"); + am.put("selectNextRow", action); + action = new ListAction("selectPreviousRowExtendSelection"); + am.put("selectPreviousRowExtendSelection", action); + action = new ListAction("selectNextRowExtendSelection"); + am.put("selectNextRowExtendSelection", action); + + action = new ListAction("selectPreviousColumn"); + am.put("selectPreviousColumn", action); + action = new ListAction("selectNextColumn"); + am.put("selectNextColumn", action); + action = new ListAction("selectPreviousColumnExtendSelection"); + am.put("selectPreviousColumnExtendSelection", action); + action = new ListAction("selectNextColumnExtendSelection"); + am.put("selectNextColumnExtendSelection", action); + + action = new ListAction("selectFirstRow"); + am.put("selectFirstRow", action); + action = new ListAction("selectLastRow"); + am.put("selectLastRow", action); + action = new ListAction("selectFirstRowExtendSelection"); + am.put("selectFirstRowExtendSelection", action); + action = new ListAction("selectLastRowExtendSelection"); + am.put("selectLastRowExtendSelection", action); + + action = new ListAction("scrollUp"); + am.put("scrollUp", action); + action = new ListAction("scrollUpExtendSelection"); + am.put("scrollUpExtendSelection", action); + action = new ListAction("scrollDown"); + am.put("scrollDown", action); + action = new ListAction("scrollDownExtendSelection"); + am.put("scrollDownExtendSelection", action); + + action = new ListAction("selectAll"); + am.put("selectAll", action); + action = new ListAction("clearSelection"); + am.put("clearSelection", action); + + am.put("copy", TransferHandler.getCopyAction()); + am.put("cut", TransferHandler.getCutAction()); + am.put("paste", TransferHandler.getPasteAction()); + + UIManager.put("List.actionMap", am); + } + + SwingUtilities.replaceUIActionMap(list, am); + } + + /** + * Uninstalls keyboard actions for this UI in the {@link JList}. + */ + protected void uninstallKeyboardActions() + { + // Uninstall the InputMap. + InputMap im = SwingUtilities.getUIInputMap(list, JComponent.WHEN_FOCUSED); + if (im instanceof UIResource) + SwingUtilities.replaceUIInputMap(list, JComponent.WHEN_FOCUSED, null); + + // Uninstall the ActionMap. + if (SwingUtilities.getUIActionMap(list) instanceof UIResource) + SwingUtilities.replaceUIActionMap(list, null); + } + + /** + * Installs the various aspects of the UI in the {@link JList}. In + * particular, calls {@link #installDefaults}, {@link #installListeners} + * and {@link #installKeyboardActions}. Also saves a reference to the + * provided component, cast to a {@link JList}. + * + * @param c The {@link JList} to install the UI into + */ + public void installUI(final JComponent c) + { + super.installUI(c); + list = (JList) c; + installDefaults(); + installListeners(); + installKeyboardActions(); + maybeUpdateLayoutState(); + } + + /** + * Uninstalls all the aspects of the UI which were installed in {@link + * #installUI}. When finished uninstalling, drops the saved reference to + * the {@link JList}. + * + * @param c Ignored; the UI is uninstalled from the {@link JList} + * reference saved during the call to {@link #installUI} + */ + public void uninstallUI(final JComponent c) + { + uninstallKeyboardActions(); + uninstallListeners(); + uninstallDefaults(); + list = null; + } + + /** + * Gets the size this list would prefer to assume. This is calculated by + * calling {@link #getCellBounds} over the entire list. + * + * @param c Ignored; uses the saved {@link JList} reference + * + * @return DOCUMENT ME! + */ + public Dimension getPreferredSize(JComponent c) + { + maybeUpdateLayoutState(); + int size = list.getModel().getSize(); + int visibleRows = list.getVisibleRowCount(); + int layoutOrientation = list.getLayoutOrientation(); + + int h; + int w; + int maxCellHeight = cellHeight; + if (maxCellHeight <= 0) + { + for (int i = 0; i < cellHeights.length; i++) + maxCellHeight = Math.max(maxCellHeight, cellHeights[i]); + } + if (layoutOrientation == JList.HORIZONTAL_WRAP) + { + if (visibleRows > 0) + { + // We cast to double here to force double divisions. + double modelSize = size; + int neededColumns = (int) Math.ceil(modelSize / visibleRows); + int adjustedRows = (int) Math.ceil(modelSize / neededColumns); + h = maxCellHeight * adjustedRows; + w = cellWidth * neededColumns; + } + else + { + int neededColumns = Math.min(1, list.getWidth() / cellWidth); + h = size / neededColumns * maxCellHeight; + w = neededColumns * cellWidth; + } + } + else if (layoutOrientation == JList.VERTICAL_WRAP) + { + if (visibleRows > 0) + h = visibleRows * maxCellHeight; + else + h = Math.max(list.getHeight(), maxCellHeight); + int neededColumns = h / maxCellHeight; + w = cellWidth * neededColumns; + } + else + { + if (list.getFixedCellWidth() > 0) + w = list.getFixedCellWidth(); + else + w = cellWidth; + if (list.getFixedCellHeight() > 0) + // FIXME: We need to add some cellVerticalMargins here, according + // to the specs. + h = list.getFixedCellHeight() * size; + else + h = maxCellHeight * size; + } + Insets insets = list.getInsets(); + Dimension retVal = new Dimension(w + insets.left + insets.right, + h + insets.top + insets.bottom); + return retVal; + } + + /** + * Paints a single cell in the list. + * + * @param g The graphics context to paint in + * @param row The row number to paint + * @param bounds The bounds of the cell to paint, assuming a coordinate + * system beginning at <code>(0,0)</code> in the upper left corner of the + * list + * @param rend A cell renderer to paint with + * @param data The data to provide to the cell renderer + * @param sel A selection model to provide to the cell renderer + * @param lead The lead selection index of the list + */ + protected void paintCell(Graphics g, int row, Rectangle bounds, + ListCellRenderer rend, ListModel data, + ListSelectionModel sel, int lead) + { + boolean isSel = list.isSelectedIndex(row); + boolean hasFocus = (list.getLeadSelectionIndex() == row) && BasicListUI.this.list.hasFocus(); + Component comp = rend.getListCellRendererComponent(list, + data.getElementAt(row), + row, isSel, hasFocus); + rendererPane.paintComponent(g, comp, list, bounds); + } + + /** + * Paints the list by repeatedly calling {@link #paintCell} for each visible + * cell in the list. + * + * @param g The graphics context to paint with + * @param c Ignored; uses the saved {@link JList} reference + */ + public void paint(Graphics g, JComponent c) + { + int nrows = list.getModel().getSize(); + if (nrows == 0) + return; + + maybeUpdateLayoutState(); + ListCellRenderer render = list.getCellRenderer(); + ListModel model = list.getModel(); + ListSelectionModel sel = list.getSelectionModel(); + int lead = sel.getLeadSelectionIndex(); + Rectangle clip = g.getClipBounds(); + + int startIndex = locationToIndex(list, new Point(clip.x, clip.y)); + int endIndex = locationToIndex(list, new Point(clip.x + clip.width, + clip.y + clip.height)); + + for (int row = startIndex; row <= endIndex; ++row) + { + Rectangle bounds = getCellBounds(list, row, row); + if (bounds != null && bounds.intersects(clip)) + paintCell(g, row, bounds, render, model, sel, lead); + } + } + + /** + * Computes the index of a list cell given a point within the list. If the + * location lies outside the bounds of the list, the greatest index in the + * list model is returned. + * + * @param l the list which on which the computation is based on + * @param location the coordinates + * + * @return the index of the list item that is located at the given + * coordinates or <code>-1</code> if the list model is empty + */ + public int locationToIndex(JList l, Point location) + { + int layoutOrientation = list.getLayoutOrientation(); + int index = -1; + switch (layoutOrientation) + { + case JList.VERTICAL: + index = convertYToRow(location.y); + break; + case JList.HORIZONTAL_WRAP: + // determine visible rows and cells per row + int maxCellHeight = getCellHeight(0); + int visibleRows = list.getHeight() / maxCellHeight; + int cellsPerRow = -1; + int numberOfItems = list.getModel().getSize(); + cellsPerRow = numberOfItems / visibleRows + 1; + + // determine index for the given location + int cellsPerColumn = numberOfItems / cellsPerRow + 1; + int gridX = Math.min(location.x / cellWidth, cellsPerRow - 1); + int gridY = Math.min(location.y / maxCellHeight, cellsPerColumn); + index = gridX + gridY * cellsPerRow; + break; + case JList.VERTICAL_WRAP: + // determine visible rows and cells per column + int maxCellHeight2 = getCellHeight(0); + int visibleRows2 = list.getHeight() / maxCellHeight2; + int numberOfItems2 = list.getModel().getSize(); + int cellsPerRow2 = numberOfItems2 / visibleRows2 + 1; + + int gridX2 = Math.min(location.x / cellWidth, cellsPerRow2 - 1); + int gridY2 = Math.min(location.y / maxCellHeight2, visibleRows2); + index = gridY2 + gridX2 * visibleRows2; + break; + } + return index; + } + + public Point indexToLocation(JList l, int index) + { + int layoutOrientation = list.getLayoutOrientation(); + Point loc = null; + switch (layoutOrientation) + { + case JList.VERTICAL: + loc = new Point(0, convertRowToY(index)); + break; + case JList.HORIZONTAL_WRAP: + // determine visible rows and cells per row + int maxCellHeight = getCellHeight(0); + int visibleRows = list.getHeight() / maxCellHeight; + int numberOfCellsPerRow = -1; + int numberOfItems = list.getModel().getSize(); + numberOfCellsPerRow = numberOfItems / visibleRows + 1; + + // compute coordinates inside the grid + int gridX = index % numberOfCellsPerRow; + int gridY = index / numberOfCellsPerRow; + int locX = gridX * cellWidth; + int locY; + locY = gridY * maxCellHeight; + loc = new Point(locX, locY); + break; + case JList.VERTICAL_WRAP: + // determine visible rows and cells per column + int maxCellHeight2 = getCellHeight(0); + int visibleRows2 = list.getHeight() / maxCellHeight2; + // compute coordinates inside the grid + if (visibleRows2 > 0) + { + int gridY2 = index % visibleRows2; + int gridX2 = index / visibleRows2; + int locX2 = gridX2 * cellWidth; + int locY2 = gridY2 * maxCellHeight2; + loc = new Point(locX2, locY2); + } + else + loc = new Point(0, convertRowToY(index)); + break; + } + return loc; + } + + /** + * Creates and returns the focus listener for this UI. + * + * @return the focus listener for this UI + */ + protected FocusListener createFocusListener() + { + return new FocusHandler(); + } + + /** + * Creates and returns the list data listener for this UI. + * + * @return the list data listener for this UI + */ + protected ListDataListener createListDataListener() + { + return new ListDataHandler(); + } + + /** + * Creates and returns the list selection listener for this UI. + * + * @return the list selection listener for this UI + */ + protected ListSelectionListener createListSelectionListener() + { + return new ListSelectionHandler(); + } + + /** + * Creates and returns the mouse input listener for this UI. + * + * @return the mouse input listener for this UI + */ + protected MouseInputListener createMouseInputListener() + { + return new MouseInputHandler(); + } + + /** + * Creates and returns the property change listener for this UI. + * + * @return the property change listener for this UI + */ + protected PropertyChangeListener createPropertyChangeListener() + { + return new PropertyChangeHandler(); + } + + /** + * Selects the next list item and force it to be visible. + */ + protected void selectNextIndex() + { + int index = list.getSelectionModel().getLeadSelectionIndex(); + if (index < list.getModel().getSize() - 1) + { + index++; + list.setSelectedIndex(index); + } + list.ensureIndexIsVisible(index); + } + + /** + * Selects the previous list item and force it to be visible. + */ + protected void selectPreviousIndex() + { + int index = list.getSelectionModel().getLeadSelectionIndex(); + if (index > 0) + { + index--; + list.setSelectedIndex(index); + } + list.ensureIndexIsVisible(index); + } +} diff --git a/libjava/classpath/javax/swing/plaf/basic/BasicLookAndFeel.java b/libjava/classpath/javax/swing/plaf/basic/BasicLookAndFeel.java new file mode 100644 index 000000000..9a1f544f6 --- /dev/null +++ b/libjava/classpath/javax/swing/plaf/basic/BasicLookAndFeel.java @@ -0,0 +1,1734 @@ +/* BasicLookAndFeel.java -- + Copyright (C) 2002, 2004, 2005, 2006, 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 java.awt.AWTEvent; +import java.awt.Color; +import java.awt.Component; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.SystemColor; +import java.awt.Toolkit; +import java.awt.event.AWTEventListener; +import java.awt.event.ActionEvent; +import java.awt.event.MouseEvent; +import java.io.IOException; +import java.io.InputStream; +import java.io.Serializable; +import java.util.Enumeration; +import java.util.ResourceBundle; + +import javax.sound.sampled.AudioInputStream; +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.Clip; +import javax.sound.sampled.LineUnavailableException; +import javax.sound.sampled.UnsupportedAudioFileException; +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.ActionMap; +import javax.swing.BorderFactory; +import javax.swing.JComponent; +import javax.swing.KeyStroke; +import javax.swing.LookAndFeel; +import javax.swing.MenuSelectionManager; +import javax.swing.UIDefaults; +import javax.swing.UIManager; +import javax.swing.border.BevelBorder; +import javax.swing.border.Border; +import javax.swing.plaf.BorderUIResource; +import javax.swing.plaf.ColorUIResource; +import javax.swing.plaf.DimensionUIResource; +import javax.swing.plaf.FontUIResource; +import javax.swing.plaf.IconUIResource; +import javax.swing.plaf.InsetsUIResource; + +/** + * A basic implementation of Swing's Look and Feel framework. This can serve + * as a base for custom look and feel implementations. + * + * @author Andrew Selkirk + */ +public abstract class BasicLookAndFeel extends LookAndFeel + implements Serializable +{ + + /** + * Helps closing menu popups when the user clicks outside of any menu area. + * This is implemented as an AWTEventListener that listens on the event + * queue directly, grabs all mouse events from there and finds out of they + * are targetted at a menu/submenu/menubar or not. If not, + * the MenuSelectionManager is messaged to close the currently opened menus, + * if any. + * + * @author Roman Kennke (kennke@aicas.com) + */ + private class PopupHelper implements AWTEventListener + { + + /** + * Receives an event from the event queue. + * + * @param event + */ + public void eventDispatched(AWTEvent event) + { + if (event instanceof MouseEvent) + { + MouseEvent mouseEvent = (MouseEvent) event; + if (mouseEvent.getID() == MouseEvent.MOUSE_PRESSED) + mousePressed(mouseEvent); + } + } + + /** + * Handles mouse pressed events from the event queue. + * + * @param ev the mouse pressed event + */ + private void mousePressed(MouseEvent ev) + { + // Autoclose all menus managed by the MenuSelectionManager. + MenuSelectionManager m = MenuSelectionManager.defaultManager(); + Component target = ev.getComponent(); + if (target instanceof Container) + target = ((Container) target).findComponentAt(ev.getPoint()); + if (m.getSelectedPath().length > 0 + && ! m.isComponentPartOfCurrentMenu(target) + && (((JComponent)target).getClientProperty(DONT_CANCEL_POPUP) == null + || !((JComponent)target).getClientProperty(DONT_CANCEL_POPUP).equals(Boolean.TRUE))) + { + m.clearSelectedPath(); + } + } + + } + + /** + * An action that can play an audio file. + * + * @author Roman Kennke (kennke@aicas.com) + */ + private class AudioAction extends AbstractAction + { + /** + * The UIDefaults key that specifies the sound. + */ + Object key; + + /** + * Creates a new AudioAction. + * + * @param key the key that describes the audio action, normally a filename + * of an audio file relative to the current package + */ + AudioAction(Object key) + { + this.key = key; + } + + /** + * Plays the sound represented by this action. + * + * @param event the action event that triggers this audio action + */ + public void actionPerformed(ActionEvent event) + { + // We only can handle strings for now. + if (key instanceof String) + { + String name = UIManager.getString(key); + InputStream stream = getClass().getResourceAsStream(name); + try + { + Clip clip = AudioSystem.getClip(); + AudioInputStream audioStream = + AudioSystem.getAudioInputStream(stream); + clip.open(audioStream); + } + catch (LineUnavailableException ex) + { + // Nothing we can do about it. + } + catch (IOException ex) + { + // Nothing we can do about it. + } + catch (UnsupportedAudioFileException e) + { + // Nothing we can do about it. + } + } + } + } + + static final long serialVersionUID = -6096995660290287879L; + + /** + * This is a key for a client property that tells the PopupHelper that + * it shouldn't close popups when the mouse event target has this + * property set. This is used when the component handles popup closing + * itself. + */ + static final String DONT_CANCEL_POPUP = "noCancelPopup"; + + /** + * Helps closing menu popups when user clicks outside of the menu area. + */ + private transient PopupHelper popupHelper; + + /** + * Maps the audio actions for this l&f. + */ + private ActionMap audioActionMap; + + /** + * Creates a new instance of the Basic look and feel. + */ + public BasicLookAndFeel() + { + // Nothing to do here. + } + + /** + * Creates and returns a new instance of the default resources for this look + * and feel. + * + * @return The UI defaults. + */ + public UIDefaults getDefaults() + { + // Variables + UIDefaults def = new UIDefaults(); + // Initialize Class Defaults + initClassDefaults(def); + // Initialize System Colour Defaults + initSystemColorDefaults(def); + // Initialize Component Defaults + initComponentDefaults(def); + // Return UI Defaults + return def; + } + + /** + * Populates the <code>defaults</code> table with mappings between class IDs + * and fully qualified class names for the UI delegates. + * + * @param defaults the defaults table (<code>null</code> not permitted). + */ + protected void initClassDefaults(UIDefaults defaults) + { + // Variables + Object[] uiDefaults; + // Initialize Class Defaults + uiDefaults = new Object[] { + "ButtonUI", "javax.swing.plaf.basic.BasicButtonUI", + "CheckBoxMenuItemUI", "javax.swing.plaf.basic.BasicCheckBoxMenuItemUI", + "CheckBoxUI", "javax.swing.plaf.basic.BasicCheckBoxUI", + "ColorChooserUI", "javax.swing.plaf.basic.BasicColorChooserUI", + "ComboBoxUI", "javax.swing.plaf.basic.BasicComboBoxUI", + "DesktopIconUI", "javax.swing.plaf.basic.BasicDesktopIconUI", + "DesktopPaneUI", "javax.swing.plaf.basic.BasicDesktopPaneUI", + "EditorPaneUI", "javax.swing.plaf.basic.BasicEditorPaneUI", + "FileChooserUI", "javax.swing.plaf.basic.BasicFileChooserUI", + "FormattedTextFieldUI", "javax.swing.plaf.basic.BasicFormattedTextFieldUI", + "InternalFrameUI", "javax.swing.plaf.basic.BasicInternalFrameUI", + "LabelUI", "javax.swing.plaf.basic.BasicLabelUI", + "ListUI", "javax.swing.plaf.basic.BasicListUI", + "MenuBarUI", "javax.swing.plaf.basic.BasicMenuBarUI", + "MenuItemUI", "javax.swing.plaf.basic.BasicMenuItemUI", + "MenuUI", "javax.swing.plaf.basic.BasicMenuUI", + "OptionPaneUI", "javax.swing.plaf.basic.BasicOptionPaneUI", + "PanelUI", "javax.swing.plaf.basic.BasicPanelUI", + "PasswordFieldUI", "javax.swing.plaf.basic.BasicPasswordFieldUI", + "PopupMenuSeparatorUI", "javax.swing.plaf.basic.BasicPopupMenuSeparatorUI", + "PopupMenuUI", "javax.swing.plaf.basic.BasicPopupMenuUI", + "ProgressBarUI", "javax.swing.plaf.basic.BasicProgressBarUI", + "RadioButtonMenuItemUI", "javax.swing.plaf.basic.BasicRadioButtonMenuItemUI", + "RadioButtonUI", "javax.swing.plaf.basic.BasicRadioButtonUI", + "RootPaneUI", "javax.swing.plaf.basic.BasicRootPaneUI", + "ScrollBarUI", "javax.swing.plaf.basic.BasicScrollBarUI", + "ScrollPaneUI", "javax.swing.plaf.basic.BasicScrollPaneUI", + "SeparatorUI", "javax.swing.plaf.basic.BasicSeparatorUI", + "SliderUI", "javax.swing.plaf.basic.BasicSliderUI", + "SplitPaneUI", "javax.swing.plaf.basic.BasicSplitPaneUI", + "SpinnerUI", "javax.swing.plaf.basic.BasicSpinnerUI", + "StandardDialogUI", "javax.swing.plaf.basic.BasicStandardDialogUI", + "TabbedPaneUI", "javax.swing.plaf.basic.BasicTabbedPaneUI", + "TableHeaderUI", "javax.swing.plaf.basic.BasicTableHeaderUI", + "TableUI", "javax.swing.plaf.basic.BasicTableUI", + "TextPaneUI", "javax.swing.plaf.basic.BasicTextPaneUI", + "TextAreaUI", "javax.swing.plaf.basic.BasicTextAreaUI", + "TextFieldUI", "javax.swing.plaf.basic.BasicTextFieldUI", + "ToggleButtonUI", "javax.swing.plaf.basic.BasicToggleButtonUI", + "ToolBarSeparatorUI", "javax.swing.plaf.basic.BasicToolBarSeparatorUI", + "ToolBarUI", "javax.swing.plaf.basic.BasicToolBarUI", + "ToolTipUI", "javax.swing.plaf.basic.BasicToolTipUI", + "TreeUI", "javax.swing.plaf.basic.BasicTreeUI", + "ViewportUI", "javax.swing.plaf.basic.BasicViewportUI" + }; + // Add Class Defaults to UI Defaults table + defaults.putDefaults(uiDefaults); + } + + /** + * Populates the <code>defaults</code> table with system color defaults. + * + * This sets up a couple of default values and passes them to + * {@link #loadSystemColors(UIDefaults, String[], boolean)}. If the + * look and feel is a native look and feel, these defaults may be overridden + * by the corresponding SystemColor constants. + * + * @param defaults the defaults table (<code>null</code> not permitted). + */ + protected void initSystemColorDefaults(UIDefaults defaults) + { + String[] defaultColors = new String[] { + "activeCaption", "#000080", + "activeCaptionBorder", "#C0C0C0", + "activeCaptionText", "#FFFFFF", + "control", "#C0C0C0", + "controlDkShadow", "#000000", + "controlHighlight", "#C0C0C0", + "controlLtHighlight", "#FFFFFF", + "controlShadow", "#808080", + "controlText", "#000000", + "desktop", "#005C5C", + "inactiveCaption", "#808080", + "inactiveCaptionBorder", "#C0C0C0", + "inactiveCaptionText", "#C0C0C0", + "info", "#FFFFE1", + "infoText", "#000000", + "menu", "#C0C0C0", + "menuText", "#000000", + "scrollbar", "#E0E0E0", + "text", "#C0C0C0", + "textHighlight", "#000080", + "textHighlightText", "#FFFFFF", + "textInactiveText", "#808080", + "textText", "#000000", + "window", "#FFFFFF", + "windowBorder", "#000000", + "windowText", "#000000" + }; + loadSystemColors(defaults, defaultColors, isNativeLookAndFeel()); + } + + /** + * Populates the <code>defaults</code> table with the system colors. If + * <code>useNative</code> is <code>true</code>, the table is populated + * with the constants in {@link SystemColor}, otherwise the + * <code>systemColors</code> parameter is decoded into the defaults table. + * The system colors array is made up of pairs, where the first entry is the + * name of the system color, and the second entry is a string denoting + * an RGB color value like "#C0C0C0", which is decoded using + * {@link Color#decode(String)}. + * + * @param defaults the defaults table (<code>null</code> not permitted). + * @param systemColors defaults to use when <code>useNative</code> is + * <code>false</code> + * @param useNative when <code>true</code>, installs the values of the + * SystemColor constants, when <code>false</code>, install the values + * from <code>systemColors</code> + */ + protected void loadSystemColors(UIDefaults defaults, String[] systemColors, + boolean useNative) + { + if (useNative) + { + defaults.put("activeCaption", + new ColorUIResource(SystemColor.ACTIVE_CAPTION)); + defaults.put("activeCaptionBorder", + new ColorUIResource(SystemColor.ACTIVE_CAPTION_BORDER)); + defaults.put("activeCaptionText", + new ColorUIResource(SystemColor.ACTIVE_CAPTION_TEXT)); + defaults.put("control", + new ColorUIResource(SystemColor.CONTROL)); + defaults.put("controlDkShadow", + new ColorUIResource(SystemColor.CONTROL_DK_SHADOW)); + defaults.put("controlHighlight", + new ColorUIResource(SystemColor.CONTROL_HIGHLIGHT)); + defaults.put("controlLtHighlight", + new ColorUIResource(SystemColor.CONTROL_LT_HIGHLIGHT)); + defaults.put("controlShadow", + new ColorUIResource(SystemColor.CONTROL_SHADOW)); + defaults.put("controlText", + new ColorUIResource(SystemColor.CONTROL_TEXT)); + defaults.put("desktop", + new ColorUIResource(SystemColor.DESKTOP)); + defaults.put("inactiveCaption", + new ColorUIResource(SystemColor.INACTIVE_CAPTION)); + defaults.put("inactiveCaptionBorder", + new ColorUIResource(SystemColor.INACTIVE_CAPTION_BORDER)); + defaults.put("inactiveCaptionText", + new ColorUIResource(SystemColor.INACTIVE_CAPTION_TEXT)); + defaults.put("info", + new ColorUIResource(SystemColor.INFO)); + defaults.put("infoText", + new ColorUIResource(SystemColor.INFO_TEXT)); + defaults.put("menu", + new ColorUIResource(SystemColor.MENU)); + defaults.put("menuText", + new ColorUIResource(SystemColor.MENU_TEXT)); + defaults.put("scrollbar", + new ColorUIResource(SystemColor.SCROLLBAR)); + defaults.put("text", + new ColorUIResource(SystemColor.TEXT)); + defaults.put("textHighlight", + new ColorUIResource(SystemColor.TEXT_HIGHLIGHT)); + defaults.put("textHighlightText", + new ColorUIResource(SystemColor.TEXT_HIGHLIGHT_TEXT)); + defaults.put("textInactiveText", + new ColorUIResource(SystemColor.TEXT_INACTIVE_TEXT)); + defaults.put("textText", + new ColorUIResource(SystemColor.TEXT_TEXT)); + defaults.put("window", + new ColorUIResource(SystemColor.WINDOW)); + defaults.put("windowBorder", + new ColorUIResource(SystemColor.WINDOW_BORDER)); + defaults.put("windowText", + new ColorUIResource(SystemColor.WINDOW_TEXT)); + } + else + { + for (int i = 0; i < systemColors.length; i += 2) + { + Color color = Color.BLACK; + try + { + color = Color.decode(systemColors[i + 1]); + } + catch (NumberFormatException e) + { + e.printStackTrace(); + } + defaults.put(systemColors[i], new ColorUIResource(color)); + } + } + } + + /** + * Loads the resource bundle in 'resources/basic' and adds the contained + * key/value pairs to the <code>defaults</code> table. + * + * @param defaults the UI defaults to load the resources into + */ + // FIXME: This method is not used atm and private and thus could be removed. + // However, I consider this method useful for providing localized + // descriptions and similar stuff and therefore think that we should use it + // instead and provide the resource bundles. + private void loadResourceBundle(UIDefaults defaults) + { + ResourceBundle bundle; + Enumeration e; + String key; + String value; + bundle = ResourceBundle.getBundle("resources/basic"); + // Process Resources + e = bundle.getKeys(); + while (e.hasMoreElements()) + { + key = (String) e.nextElement(); + value = bundle.getString(key); + defaults.put(key, value); + } + } + + /** + * Populates the <code>defaults</code> table with UI default values for + * colors, fonts, keybindings and much more. + * + * @param defaults the defaults table (<code>null</code> not permitted). + */ + protected void initComponentDefaults(UIDefaults defaults) + { + Object[] uiDefaults; + + Color highLight = new Color(249, 247, 246); + Color light = new Color(239, 235, 231); + Color shadow = new Color(139, 136, 134); + Color darkShadow = new Color(16, 16, 16); + + uiDefaults = new Object[] { + + "AbstractUndoableEdit.undoText", "Undo", + "AbstractUndoableEdit.redoText", "Redo", + "Button.background", new ColorUIResource(Color.LIGHT_GRAY), + "Button.border", + new UIDefaults.LazyValue() + { + public Object createValue(UIDefaults table) + { + return BasicBorders.getButtonBorder(); + } + }, + "Button.darkShadow", new ColorUIResource(Color.BLACK), + "Button.font", new FontUIResource("Dialog", Font.PLAIN, 12), + "Button.foreground", new ColorUIResource(Color.BLACK), + "Button.focusInputMap", new UIDefaults.LazyInputMap(new Object[] { + KeyStroke.getKeyStroke("SPACE"), "pressed", + KeyStroke.getKeyStroke("released SPACE"), "released" + }), + "Button.highlight", new ColorUIResource(Color.WHITE), + "Button.light", new ColorUIResource(Color.LIGHT_GRAY), + "Button.margin", new InsetsUIResource(2, 14, 2, 14), + "Button.shadow", new ColorUIResource(Color.GRAY), + "Button.textIconGap", new Integer(4), + "Button.textShiftOffset", new Integer(0), + "CheckBox.background", new ColorUIResource(new Color(204, 204, 204)), + "CheckBox.border", new BorderUIResource.CompoundBorderUIResource(null, + null), + "CheckBox.focusInputMap", new UIDefaults.LazyInputMap(new Object[] { + KeyStroke.getKeyStroke("SPACE"), "pressed", + KeyStroke.getKeyStroke("released SPACE"), "released" + }), + "CheckBox.font", new FontUIResource("Dialog", Font.PLAIN, 12), + "CheckBox.foreground", new ColorUIResource(darkShadow), + "CheckBox.icon", + new UIDefaults.LazyValue() + { + public Object createValue(UIDefaults def) + { + return BasicIconFactory.getCheckBoxIcon(); + } + }, + "CheckBox.checkIcon", + new UIDefaults.LazyValue() + { + public Object createValue(UIDefaults def) + { + return BasicIconFactory.getMenuItemCheckIcon(); + } + }, + "CheckBox.margin", new InsetsUIResource(2, 2, 2, 2), + "CheckBox.textIconGap", new Integer(4), + "CheckBox.textShiftOffset", new Integer(0), + "CheckBoxMenuItem.acceleratorFont", new FontUIResource("Dialog", + Font.PLAIN, 12), + "CheckBoxMenuItem.acceleratorForeground", + new ColorUIResource(new Color(16, 16, 16)), + "CheckBoxMenuItem.acceleratorSelectionForeground", + new ColorUIResource(Color.white), + "CheckBoxMenuItem.arrowIcon", BasicIconFactory.getMenuItemArrowIcon(), + "CheckBoxMenuItem.background", new ColorUIResource(light), + "CheckBoxMenuItem.border", new BasicBorders.MarginBorder(), + "CheckBoxMenuItem.borderPainted", Boolean.FALSE, + "CheckBoxMenuItem.checkIcon", + new UIDefaults.LazyValue() + { + public Object createValue(UIDefaults def) + { + return BasicIconFactory.getCheckBoxMenuItemIcon(); + } + }, + "CheckBoxMenuItem.font", new FontUIResource("Dialog", Font.PLAIN, 12), + "CheckBoxMenuItem.foreground", new ColorUIResource(darkShadow), + "CheckBoxMenuItem.margin", new InsetsUIResource(2, 2, 2, 2), + "CheckBoxMenuItem.selectionBackground", new ColorUIResource(Color.black), + "CheckBoxMenuItem.selectionForeground", new ColorUIResource(Color.white), + "ColorChooser.background", new ColorUIResource(light), + "ColorChooser.cancelText", "Cancel", + "ColorChooser.font", new FontUIResource("Dialog", Font.PLAIN, 12), + "ColorChooser.foreground", new ColorUIResource(darkShadow), + "ColorChooser.hsbBlueText", "B", + "ColorChooser.hsbBrightnessText", "B", + "ColorChooser.hsbGreenText", "G", + "ColorChooser.hsbHueText", "H", + "ColorChooser.hsbNameText", "HSB", + "ColorChooser.hsbRedText", "R", + "ColorChooser.hsbSaturationText", "S", + "ColorChooser.okText", "OK", + "ColorChooser.previewText", "Preview", + "ColorChooser.resetText", "Reset", + "ColorChooser.rgbBlueMnemonic", "66", + "ColorChooser.rgbBlueText", "Blue", + "ColorChooser.rgbGreenMnemonic", "78", + "ColorChooser.rgbGreenText", "Green", + "ColorChooser.rgbNameText", "RGB", + "ColorChooser.rgbRedMnemonic", "68", + "ColorChooser.rgbRedText", "Red", + "ColorChooser.sampleText", "Sample Text Sample Text", + "ColorChooser.swatchesDefaultRecentColor", new ColorUIResource(light), + "ColorChooser.swatchesNameText", "Swatches", + "ColorChooser.swatchesRecentSwatchSize", new Dimension(10, 10), + "ColorChooser.swatchesRecentText", "Recent:", + "ColorChooser.swatchesSwatchSize", new Dimension(10, 10), + "ComboBox.ancestorInputMap", new UIDefaults.LazyInputMap(new Object[] { + "ESCAPE", "hidePopup", + "PAGE_UP", "pageUpPassThrough", + "PAGE_DOWN", "pageDownPassThrough", + "HOME", "homePassThrough", + "END", "endPassThrough" + }), + "ComboBox.background", new ColorUIResource(Color.white), + "ComboBox.buttonBackground", new ColorUIResource(light), + "ComboBox.buttonDarkShadow", new ColorUIResource(darkShadow), + "ComboBox.buttonHighlight", new ColorUIResource(highLight), + "ComboBox.buttonShadow", new ColorUIResource(shadow), + "ComboBox.disabledBackground", new ColorUIResource(light), + "ComboBox.disabledForeground", new ColorUIResource(Color.gray), + "ComboBox.font", new FontUIResource("SansSerif", Font.PLAIN, 12), + "ComboBox.foreground", new ColorUIResource(Color.black), + "ComboBox.selectionBackground", new ColorUIResource(0, 0, 128), + "ComboBox.selectionForeground", new ColorUIResource(Color.white), + "Desktop.ancestorInputMap", new UIDefaults.LazyInputMap(new Object[] { + "KP_LEFT", "left", + "KP_RIGHT", "right", + "ctrl F5", "restore", + "LEFT", "left", + "ctrl alt F6", "selectNextFrame", + "UP", "up", + "ctrl F6", "selectNextFrame", + "RIGHT", "right", + "DOWN", "down", + "ctrl F7", "move", + "ctrl F8", "resize", + "ESCAPE", "escape", + "ctrl TAB", "selectNextFrame", + "ctrl F9", "minimize", + "KP_UP", "up", + "ctrl F4", "close", + "KP_DOWN", "down", + "ctrl F10", "maximize", + "ctrl alt shift F6", "selectPreviousFrame" + }), + "DesktopIcon.border", new BorderUIResource.CompoundBorderUIResource(null, + null), + "EditorPane.background", new ColorUIResource(Color.white), + "EditorPane.border", BasicBorders.getMarginBorder(), + "EditorPane.caretBlinkRate", new Integer(500), + "EditorPane.caretForeground", new ColorUIResource(Color.black), + "EditorPane.font", new FontUIResource("Serif", Font.PLAIN, 12), + "EditorPane.foreground", new ColorUIResource(Color.black), + "EditorPane.inactiveForeground", new ColorUIResource(Color.gray), + "EditorPane.focusInputMap", new UIDefaults.LazyInputMap(new Object[] { + KeyStroke.getKeyStroke("shift UP"), "selection-up", + KeyStroke.getKeyStroke("ctrl RIGHT"), "caret-next-word", + KeyStroke.getKeyStroke("shift ctrl LEFT"), "selection-previous-word", + KeyStroke.getKeyStroke("shift KP_UP"), "selection-up", + KeyStroke.getKeyStroke("DOWN"), "caret-down", + KeyStroke.getKeyStroke("shift ctrl T"), "previous-link-action", + KeyStroke.getKeyStroke("ctrl LEFT"), "caret-previous-word", + KeyStroke.getKeyStroke("CUT"), "cut-to-clipboard", + KeyStroke.getKeyStroke("END"), "caret-end-line", + KeyStroke.getKeyStroke("shift PAGE_UP"), "selection-page-up", + KeyStroke.getKeyStroke("KP_UP"), "caret-up", + KeyStroke.getKeyStroke("DELETE"), "delete-next", + KeyStroke.getKeyStroke("ctrl HOME"), "caret-begin", + KeyStroke.getKeyStroke("shift LEFT"), "selection-backward", + KeyStroke.getKeyStroke("ctrl END"), "caret-end", + KeyStroke.getKeyStroke("BACK_SPACE"), "delete-previous", + KeyStroke.getKeyStroke("shift ctrl RIGHT"), "selection-next-word", + KeyStroke.getKeyStroke("LEFT"), "caret-backward", + KeyStroke.getKeyStroke("KP_LEFT"), "caret-backward", + KeyStroke.getKeyStroke("shift KP_RIGHT"), "selection-forward", + KeyStroke.getKeyStroke("ctrl SPACE"), "activate-link-action", + KeyStroke.getKeyStroke("ctrl H"), "delete-previous", + KeyStroke.getKeyStroke("ctrl BACK_SLASH"), "unselect", + KeyStroke.getKeyStroke("ENTER"), "insert-break", + KeyStroke.getKeyStroke("shift HOME"), "selection-begin-line", + KeyStroke.getKeyStroke("RIGHT"), "caret-forward", + KeyStroke.getKeyStroke("shift ctrl PAGE_UP"), "selection-page-left", + KeyStroke.getKeyStroke("shift DOWN"), "selection-down", + KeyStroke.getKeyStroke("PAGE_DOWN"), "page-down", + KeyStroke.getKeyStroke("shift KP_LEFT"), "selection-backward", + KeyStroke.getKeyStroke("shift ctrl O"), "toggle-componentOrientation", + KeyStroke.getKeyStroke("ctrl X"), "cut-to-clipboard", + KeyStroke.getKeyStroke("shift ctrl PAGE_DOWN"), "selection-page-right", + KeyStroke.getKeyStroke("ctrl C"), "copy-to-clipboard", + KeyStroke.getKeyStroke("ctrl KP_RIGHT"), "caret-next-word", + KeyStroke.getKeyStroke("shift END"), "selection-end-line", + KeyStroke.getKeyStroke("ctrl KP_LEFT"), "caret-previous-word", + KeyStroke.getKeyStroke("HOME"), "caret-begin-line", + KeyStroke.getKeyStroke("ctrl V"), "paste-from-clipboard", + KeyStroke.getKeyStroke("KP_DOWN"), "caret-down", + KeyStroke.getKeyStroke("ctrl A"), "select-all", + KeyStroke.getKeyStroke("shift RIGHT"), "selection-forward", + KeyStroke.getKeyStroke("shift ctrl END"), "selection-end", + KeyStroke.getKeyStroke("COPY"), "copy-to-clipboard", + KeyStroke.getKeyStroke("shift ctrl KP_LEFT"), "selection-previous-word", + KeyStroke.getKeyStroke("ctrl T"), "next-link-action", + KeyStroke.getKeyStroke("shift KP_DOWN"), "selection-down", + KeyStroke.getKeyStroke("TAB"), "insert-tab", + KeyStroke.getKeyStroke("UP"), "caret-up", + KeyStroke.getKeyStroke("shift ctrl HOME"), "selection-begin", + KeyStroke.getKeyStroke("shift PAGE_DOWN"), "selection-page-down", + KeyStroke.getKeyStroke("KP_RIGHT"), "caret-forward", + KeyStroke.getKeyStroke("shift ctrl KP_RIGHT"), "selection-next-word", + KeyStroke.getKeyStroke("PAGE_UP"), "page-up", + KeyStroke.getKeyStroke("PASTE"), "paste-from-clipboard" + }), + "EditorPane.margin", new InsetsUIResource(3, 3, 3, 3), + "EditorPane.selectionBackground", new ColorUIResource(Color.black), + "EditorPane.selectionForeground", new ColorUIResource(Color.white), + "FileChooser.acceptAllFileFilterText", "All Files (*.*)", + "FileChooser.ancestorInputMap", new UIDefaults.LazyInputMap(new Object[] { + "ESCAPE", "cancelSelection" + }), + "FileChooser.cancelButtonMnemonic", "67", + "FileChooser.cancelButtonText", "Cancel", + "FileChooser.cancelButtonToolTipText", "Abort file chooser dialog", + "FileChooser.directoryDescriptionText", "Directory", + "FileChooser.fileDescriptionText", "Generic File", + "FileChooser.directoryOpenButtonMnemonic", "79", + "FileChooser.helpButtonMnemonic", "72", + "FileChooser.helpButtonText", "Help", + "FileChooser.helpButtonToolTipText", "FileChooser help", + "FileChooser.newFolderErrorSeparator", ":", + "FileChooser.newFolderErrorText", "Error creating new folder", + "FileChooser.openButtonMnemonic", "79", + "FileChooser.openButtonText", "Open", + "FileChooser.openButtonToolTipText", "Open selected file", + "FileChooser.saveButtonMnemonic", "83", + "FileChooser.saveButtonText", "Save", + "FileChooser.saveButtonToolTipText", "Save selected file", + "FileChooser.updateButtonMnemonic", "85", + "FileChooser.updateButtonText", "Update", + "FileChooser.updateButtonToolTipText", "Update directory listing", + "FocusManagerClassName", "TODO", + "FormattedTextField.background", new ColorUIResource(light), + "FormattedTextField.caretForeground", new ColorUIResource(Color.black), + "FormattedTextField.margin", new InsetsUIResource(0, 0, 0, 0), + "FormattedTextField.caretBlinkRate", new Integer(500), + "FormattedTextField.font", + new FontUIResource("SansSerif", Font.PLAIN, 12), + "FormattedTextField.foreground", new ColorUIResource(Color.black), + "FormattedTextField.focusInputMap", new UIDefaults.LazyInputMap(new Object[] { + KeyStroke.getKeyStroke("KP_UP"), "increment", + KeyStroke.getKeyStroke("END"), "caret-end-line", + KeyStroke.getKeyStroke("shift ctrl O"), "toggle-componentOrientation", + KeyStroke.getKeyStroke("shift KP_LEFT"), "selection-backward", + KeyStroke.getKeyStroke("shift RIGHT"), "selection-forward", + KeyStroke.getKeyStroke("KP_DOWN"), "decrement", + KeyStroke.getKeyStroke("HOME"), "caret-begin-line", + KeyStroke.getKeyStroke("ctrl V"), "paste-from-clipboard", + KeyStroke.getKeyStroke("ctrl H"), "delete-previous", + KeyStroke.getKeyStroke("KP_LEFT"), "caret-backward", + KeyStroke.getKeyStroke("LEFT"), "caret-backward", + KeyStroke.getKeyStroke("ctrl X"), "cut-to-clipboard", + KeyStroke.getKeyStroke("KP_RIGHT"), "caret-forward", + KeyStroke.getKeyStroke("UP"), "increment", + KeyStroke.getKeyStroke("shift ctrl KP_RIGHT"), "selection-next-word", + KeyStroke.getKeyStroke("COPY"), "copy-to-clipboard", + KeyStroke.getKeyStroke("shift HOME"), "selection-begin-line", + KeyStroke.getKeyStroke("ESCAPE"), "reset-field-edit", + KeyStroke.getKeyStroke("RIGHT"), "caret-forward", + KeyStroke.getKeyStroke("shift ctrl LEFT"), "selection-previous-word", + KeyStroke.getKeyStroke("ctrl KP_LEFT"), "caret-previous-word", + KeyStroke.getKeyStroke("DOWN"), "decrement", + KeyStroke.getKeyStroke("ctrl KP_RIGHT"), "caret-next-word", + KeyStroke.getKeyStroke("PASTE"), "paste-from-clipboard", + KeyStroke.getKeyStroke("shift ctrl RIGHT"), "selection-next-word", + KeyStroke.getKeyStroke("ctrl BACK_SLASH"), "unselect", + KeyStroke.getKeyStroke("ctrl A"), "select-all", + KeyStroke.getKeyStroke("shift KP_RIGHT"), "selection-forward", + KeyStroke.getKeyStroke("CUT"), "cut-to-clipboard", + KeyStroke.getKeyStroke("ctrl LEFT"), "caret-previous-word", + KeyStroke.getKeyStroke("BACK_SPACE"), "delete-previous", + KeyStroke.getKeyStroke("shift ctrl KP_LEFT"), "selection-previous-word", + KeyStroke.getKeyStroke("ctrl C"), "copy-to-clipboard", + KeyStroke.getKeyStroke("shift END"), "selection-end-line", + KeyStroke.getKeyStroke("ctrl RIGHT"), "caret-next-word", + KeyStroke.getKeyStroke("DELETE"), "delete-next", + KeyStroke.getKeyStroke("ENTER"), "notify-field-accept", + KeyStroke.getKeyStroke("shift LEFT"), "selection-backward" + }), + "FormattedTextField.inactiveBackground", new ColorUIResource(light), + "FormattedTextField.inactiveForeground", new ColorUIResource(Color.gray), + "FormattedTextField.selectionBackground", + new ColorUIResource(Color.black), + "FormattedTextField.selectionForeground", + new ColorUIResource(Color.white), + "FormView.resetButtonText", "Reset", + "FormView.submitButtonText", "Submit Query", + "InternalFrame.activeTitleBackground", new ColorUIResource(0, 0, 128), + "InternalFrame.activeTitleForeground", new ColorUIResource(Color.white), + "InternalFrame.border", + new UIDefaults.LazyValue() + { + public Object createValue(UIDefaults table) + { + Color lineColor = new Color(238, 238, 238); + Border inner = BorderFactory.createLineBorder(lineColor, 1); + Color shadowInner = new Color(184, 207, 229); + Color shadowOuter = new Color(122, 138, 153); + Border outer = BorderFactory.createBevelBorder(BevelBorder.RAISED, + Color.WHITE, + Color.WHITE, + shadowOuter, + shadowInner); + Border border = new BorderUIResource.CompoundBorderUIResource(outer, + inner); + return border; + } + }, + "InternalFrame.borderColor", new ColorUIResource(light), + "InternalFrame.borderDarkShadow", new ColorUIResource(Color.BLACK), + "InternalFrame.borderHighlight", new ColorUIResource(Color.WHITE), + "InternalFrame.borderLight", new ColorUIResource(Color.LIGHT_GRAY), + "InternalFrame.borderShadow", new ColorUIResource(Color.GRAY), + "InternalFrame.closeIcon", BasicIconFactory.createEmptyFrameIcon(), + "InternalFrame.icon", + new UIDefaults.LazyValue() + { + public Object createValue(UIDefaults def) + { + return new IconUIResource(BasicIconFactory.createEmptyFrameIcon()); + } + }, + "InternalFrame.iconifyIcon", BasicIconFactory.createEmptyFrameIcon(), + "InternalFrame.inactiveTitleBackground", new ColorUIResource(Color.gray), + "InternalFrame.inactiveTitleForeground", + new ColorUIResource(Color.lightGray), + "InternalFrame.maximizeIcon", BasicIconFactory.createEmptyFrameIcon(), + "InternalFrame.minimizeIcon", BasicIconFactory.createEmptyFrameIcon(), + "InternalFrame.titleFont", new FontUIResource("Dialog", Font.BOLD, 12), + "InternalFrame.windowBindings", new Object[] { + "shift ESCAPE", "showSystemMenu", + "ctrl SPACE", "showSystemMenu", + "ESCAPE", "showSystemMenu" + }, + "Label.background", new ColorUIResource(light), + "Label.disabledForeground", new ColorUIResource(Color.white), + "Label.disabledShadow", new ColorUIResource(shadow), + "Label.font", new FontUIResource("Dialog", Font.PLAIN, 12), + "Label.foreground", new ColorUIResource(darkShadow), + "List.background", new ColorUIResource(Color.white), + "List.border", new BasicBorders.MarginBorder(), + "List.focusInputMap", new UIDefaults.LazyInputMap(new Object[] { + KeyStroke.getKeyStroke("ctrl DOWN"), "selectNextRowChangeLead", + KeyStroke.getKeyStroke("shift UP"), "selectPreviousRowExtendSelection", + KeyStroke.getKeyStroke("ctrl RIGHT"), "selectNextColumnChangeLead", + KeyStroke.getKeyStroke("shift ctrl LEFT"), "selectPreviousColumnExtendSelection", + KeyStroke.getKeyStroke("shift KP_UP"), "selectPreviousRowExtendSelection", + KeyStroke.getKeyStroke("DOWN"), "selectNextRow", + KeyStroke.getKeyStroke("ctrl UP"), "selectPreviousRowChangeLead", + KeyStroke.getKeyStroke("ctrl LEFT"), "selectPreviousColumnChangeLead", + KeyStroke.getKeyStroke("CUT"), "cut", + KeyStroke.getKeyStroke("END"), "selectLastRow", + KeyStroke.getKeyStroke("shift PAGE_UP"), "scrollUpExtendSelection", + KeyStroke.getKeyStroke("KP_UP"), "selectPreviousRow", + KeyStroke.getKeyStroke("shift ctrl UP"), "selectPreviousRowExtendSelection", + KeyStroke.getKeyStroke("ctrl HOME"), "selectFirstRowChangeLead", + KeyStroke.getKeyStroke("shift LEFT"), "selectPreviousColumnExtendSelection", + KeyStroke.getKeyStroke("ctrl END"), "selectLastRowChangeLead", + KeyStroke.getKeyStroke("ctrl PAGE_DOWN"), "scrollDownChangeLead", + KeyStroke.getKeyStroke("shift ctrl RIGHT"), "selectNextColumnExtendSelection", + KeyStroke.getKeyStroke("LEFT"), "selectPreviousColumn", + KeyStroke.getKeyStroke("ctrl PAGE_UP"), "scrollUpChangeLead", + KeyStroke.getKeyStroke("KP_LEFT"), "selectPreviousColumn", + KeyStroke.getKeyStroke("shift KP_RIGHT"), "selectNextColumnExtendSelection", + KeyStroke.getKeyStroke("SPACE"), "addToSelection", + KeyStroke.getKeyStroke("ctrl SPACE"), "toggleAndAnchor", + KeyStroke.getKeyStroke("shift SPACE"), "extendTo", + KeyStroke.getKeyStroke("shift ctrl SPACE"), "moveSelectionTo", + KeyStroke.getKeyStroke("shift ctrl DOWN"), "selectNextRowExtendSelection", + KeyStroke.getKeyStroke("ctrl BACK_SLASH"), "clearSelection", + KeyStroke.getKeyStroke("shift HOME"), "selectFirstRowExtendSelection", + KeyStroke.getKeyStroke("RIGHT"), "selectNextColumn", + KeyStroke.getKeyStroke("shift ctrl PAGE_UP"), "scrollUpExtendSelection", + KeyStroke.getKeyStroke("shift DOWN"), "selectNextRowExtendSelection", + KeyStroke.getKeyStroke("PAGE_DOWN"), "scrollDown", + KeyStroke.getKeyStroke("shift ctrl KP_UP"), "selectPreviousRowExtendSelection", + KeyStroke.getKeyStroke("shift KP_LEFT"), "selectPreviousColumnExtendSelection", + KeyStroke.getKeyStroke("ctrl X"), "cut", + KeyStroke.getKeyStroke("shift ctrl PAGE_DOWN"), "scrollDownExtendSelection", + KeyStroke.getKeyStroke("ctrl SLASH"), "selectAll", + KeyStroke.getKeyStroke("ctrl C"), "copy", + KeyStroke.getKeyStroke("ctrl KP_RIGHT"), "selectNextColumnChangeLead", + KeyStroke.getKeyStroke("shift END"), "selectLastRowExtendSelection", + KeyStroke.getKeyStroke("shift ctrl KP_DOWN"), "selectNextRowExtendSelection", + KeyStroke.getKeyStroke("ctrl KP_LEFT"), "selectPreviousColumnChangeLead", + KeyStroke.getKeyStroke("HOME"), "selectFirstRow", + KeyStroke.getKeyStroke("ctrl V"), "paste", + KeyStroke.getKeyStroke("KP_DOWN"), "selectNextRow", + KeyStroke.getKeyStroke("ctrl KP_DOWN"), "selectNextRowChangeLead", + KeyStroke.getKeyStroke("shift RIGHT"), "selectNextColumnExtendSelection", + KeyStroke.getKeyStroke("ctrl A"), "selectAll", + KeyStroke.getKeyStroke("shift ctrl END"), "selectLastRowExtendSelection", + KeyStroke.getKeyStroke("COPY"), "copy", + KeyStroke.getKeyStroke("ctrl KP_UP"), "selectPreviousRowChangeLead", + KeyStroke.getKeyStroke("shift ctrl KP_LEFT"), "selectPreviousColumnExtendSelection", + KeyStroke.getKeyStroke("shift KP_DOWN"), "selectNextRowExtendSelection", + KeyStroke.getKeyStroke("UP"), "selectPreviousRow", + KeyStroke.getKeyStroke("shift ctrl HOME"), "selectFirstRowExtendSelection", + KeyStroke.getKeyStroke("shift PAGE_DOWN"), "scrollDownExtendSelection", + KeyStroke.getKeyStroke("KP_RIGHT"), "selectNextColumn", + KeyStroke.getKeyStroke("shift ctrl KP_RIGHT"), "selectNextColumnExtendSelection", + KeyStroke.getKeyStroke("PAGE_UP"), "scrollUp", + KeyStroke.getKeyStroke("PASTE"), "paste" + }), + "List.font", new FontUIResource("Dialog", Font.PLAIN, 12), + "List.foreground", new ColorUIResource(Color.black), + "List.selectionBackground", new ColorUIResource(0, 0, 128), + "List.selectionForeground", new ColorUIResource(Color.white), + "List.focusCellHighlightBorder", + new BorderUIResource. + LineBorderUIResource(new ColorUIResource(Color.yellow)), + "Menu.acceleratorFont", new FontUIResource("Dialog", Font.PLAIN, 12), + "Menu.crossMenuMnemonic", Boolean.TRUE, + "Menu.acceleratorForeground", new ColorUIResource(darkShadow), + "Menu.acceleratorSelectionForeground", new ColorUIResource(Color.white), + "Menu.arrowIcon", BasicIconFactory.getMenuArrowIcon(), + "Menu.background", new ColorUIResource(light), + "Menu.border", new BasicBorders.MarginBorder(), + "Menu.borderPainted", Boolean.FALSE, + "Menu.checkIcon", BasicIconFactory.getMenuItemCheckIcon(), + "Menu.consumesTabs", Boolean.TRUE, + "Menu.font", new FontUIResource("Dialog", Font.PLAIN, 12), + "Menu.foreground", new ColorUIResource(darkShadow), + "Menu.margin", new InsetsUIResource(2, 2, 2, 2), + "Menu.selectedWindowInputMapBindings", new Object[] { + "ESCAPE", "cancel", + "DOWN", "selectNext", + "KP_DOWN", "selectNext", + "UP", "selectPrevious", + "KP_UP", "selectPrevious", + "LEFT", "selectParent", + "KP_LEFT", "selectParent", + "RIGHT", "selectChild", + "KP_RIGHT", "selectChild", + "ENTER", "return", + "SPACE", "return" + }, + "Menu.menuPopupOffsetX", new Integer(0), + "Menu.menuPopupOffsetY", new Integer(0), + "Menu.submenuPopupOffsetX", new Integer(0), + "Menu.submenuPopupOffsetY", new Integer(0), + "Menu.selectionBackground", new ColorUIResource(Color.black), + "Menu.selectionForeground", new ColorUIResource(Color.white), + "MenuBar.background", new ColorUIResource(light), + "MenuBar.border", new BasicBorders.MenuBarBorder(null, null), + "MenuBar.font", new FontUIResource("Dialog", Font.PLAIN, 12), + "MenuBar.foreground", new ColorUIResource(darkShadow), + "MenuBar.highlight", new ColorUIResource(highLight), + "MenuBar.shadow", new ColorUIResource(shadow), + "MenuBar.windowBindings", new Object[] { + "F10", "takeFocus" + }, + "MenuItem.acceleratorDelimiter", "+", + "MenuItem.acceleratorFont", new FontUIResource("Dialog", Font.PLAIN, 12), + "MenuItem.acceleratorForeground", new ColorUIResource(darkShadow), + "MenuItem.acceleratorSelectionForeground", + new ColorUIResource(Color.white), + "MenuItem.arrowIcon", BasicIconFactory.getMenuItemArrowIcon(), + "MenuItem.background", new ColorUIResource(light), + "MenuItem.border", new BasicBorders.MarginBorder(), + "MenuItem.borderPainted", Boolean.FALSE, + "MenuItem.font", new FontUIResource("Dialog", Font.PLAIN, 12), + "MenuItem.foreground", new ColorUIResource(darkShadow), + "MenuItem.margin", new InsetsUIResource(2, 2, 2, 2), + "MenuItem.selectionBackground", new ColorUIResource(Color.black), + "MenuItem.selectionForeground", new ColorUIResource(Color.white), + "OptionPane.background", new ColorUIResource(light), + "OptionPane.border", + new BorderUIResource.EmptyBorderUIResource(0, 0, 0, 0), + "OptionPane.buttonAreaBorder", + new BorderUIResource.EmptyBorderUIResource(0, 0, 0, 0), + "OptionPane.buttonClickThreshhold", new Integer(500), + "OptionPane.cancelButtonText", "Cancel", + "OptionPane.font", new FontUIResource("Dialog", Font.PLAIN, 12), + "OptionPane.foreground", new ColorUIResource(darkShadow), + "OptionPane.messageAreaBorder", + new BorderUIResource.EmptyBorderUIResource(0, 0, 0, 0), + "OptionPane.messageForeground", new ColorUIResource(darkShadow), + "OptionPane.minimumSize", + new DimensionUIResource(BasicOptionPaneUI.MinimumWidth, + BasicOptionPaneUI.MinimumHeight), + "OptionPane.noButtonText", "No", + "OptionPane.okButtonText", "OK", + "OptionPane.windowBindings", new Object[] { + "ESCAPE", "close" + }, + "OptionPane.yesButtonText", "Yes", + "Panel.background", new ColorUIResource(light), + "Panel.font", new FontUIResource("Dialog", Font.PLAIN, 12), + "Panel.foreground", new ColorUIResource(Color.black), + "PasswordField.background", new ColorUIResource(light), + "PasswordField.border", new BasicBorders.FieldBorder(null, null, + null, null), + "PasswordField.caretBlinkRate", new Integer(500), + "PasswordField.caretForeground", new ColorUIResource(Color.black), + "PasswordField.font", new FontUIResource("MonoSpaced", Font.PLAIN, 12), + "PasswordField.foreground", new ColorUIResource(Color.black), + "PasswordField.inactiveBackground", new ColorUIResource(light), + "PasswordField.inactiveForeground", new ColorUIResource(Color.gray), + "PasswordField.focusInputMap", new UIDefaults.LazyInputMap(new Object[] { + KeyStroke.getKeyStroke("END"), "caret-end-line", + KeyStroke.getKeyStroke("shift ctrl O"), "toggle-componentOrientation", + KeyStroke.getKeyStroke("shift KP_LEFT"), "selection-backward", + KeyStroke.getKeyStroke("shift RIGHT"), "selection-forward", + KeyStroke.getKeyStroke("HOME"), "caret-begin-line", + KeyStroke.getKeyStroke("ctrl V"), "paste-from-clipboard", + KeyStroke.getKeyStroke("ctrl H"), "delete-previous", + KeyStroke.getKeyStroke("KP_LEFT"), "caret-backward", + KeyStroke.getKeyStroke("LEFT"), "caret-backward", + KeyStroke.getKeyStroke("ctrl X"), "cut-to-clipboard", + KeyStroke.getKeyStroke("KP_RIGHT"), "caret-forward", + KeyStroke.getKeyStroke("shift ctrl KP_RIGHT"), "selection-end-line", + KeyStroke.getKeyStroke("COPY"), "copy-to-clipboard", + KeyStroke.getKeyStroke("shift HOME"), "selection-begin-line", + KeyStroke.getKeyStroke("RIGHT"), "caret-forward", + KeyStroke.getKeyStroke("shift ctrl LEFT"), "selection-begin-line", + KeyStroke.getKeyStroke("ctrl KP_LEFT"), "caret-begin-line", + KeyStroke.getKeyStroke("ctrl KP_RIGHT"), "caret-end-line", + KeyStroke.getKeyStroke("PASTE"), "paste-from-clipboard", + KeyStroke.getKeyStroke("shift ctrl RIGHT"), "selection-end-line", + KeyStroke.getKeyStroke("ctrl BACK_SLASH"), "unselect", + KeyStroke.getKeyStroke("ctrl A"), "select-all", + KeyStroke.getKeyStroke("shift KP_RIGHT"), "selection-forward", + KeyStroke.getKeyStroke("CUT"), "cut-to-clipboard", + KeyStroke.getKeyStroke("ctrl LEFT"), "caret-begin-line", + KeyStroke.getKeyStroke("BACK_SPACE"), "delete-previous", + KeyStroke.getKeyStroke("shift ctrl KP_LEFT"), "selection-begin-line", + KeyStroke.getKeyStroke("ctrl C"), "copy-to-clipboard", + KeyStroke.getKeyStroke("shift END"), "selection-end-line", + KeyStroke.getKeyStroke("ctrl RIGHT"), "caret-end-line", + KeyStroke.getKeyStroke("DELETE"), "delete-next", + KeyStroke.getKeyStroke("ENTER"), "notify-field-accept", + KeyStroke.getKeyStroke("shift LEFT"), "selection-backward" + }), + "PasswordField.margin", new InsetsUIResource(0, 0, 0, 0), + "PasswordField.selectionBackground", new ColorUIResource(Color.black), + "PasswordField.selectionForeground", new ColorUIResource(Color.white), + "PopupMenu.background", new ColorUIResource(light), + "PopupMenu.border", new BorderUIResource.BevelBorderUIResource(0), + "PopupMenu.font", new FontUIResource("Dialog", Font.PLAIN, 12), + "PopupMenu.foreground", new ColorUIResource(darkShadow), + "PopupMenu.selectedWindowInputMapBindings", + new Object[] {"ESCAPE", "cancel", + "DOWN", "selectNext", + "KP_DOWN", "selectNext", + "UP", "selectPrevious", + "KP_UP", "selectPrevious", + "LEFT", "selectParent", + "KP_LEFT", "selectParent", + "RIGHT", "selectChild", + "KP_RIGHT", "selectChild", + "ENTER", "return", + "SPACE", "return" + }, + "PopupMenu.selectedWindowInputMapBindings.RightToLeft", + new Object[] {"LEFT", "selectChild", + "KP_LEFT", "selectChild", + "RIGHT", "selectParent", + "KP_RIGHT", "selectParent", + }, + "ProgressBar.background", new ColorUIResource(Color.LIGHT_GRAY), + "ProgressBar.border", + new BorderUIResource.LineBorderUIResource(Color.GREEN, 2), + "ProgressBar.cellLength", new Integer(1), + "ProgressBar.cellSpacing", new Integer(0), + "ProgressBar.font", new FontUIResource("Dialog", Font.PLAIN, 12), + "ProgressBar.foreground", new ColorUIResource(0, 0, 128), + "ProgressBar.selectionBackground", new ColorUIResource(0, 0, 128), + "ProgressBar.selectionForeground", new ColorUIResource(Color.LIGHT_GRAY), + "ProgressBar.repaintInterval", new Integer(50), + "ProgressBar.cycleTime", new Integer(3000), + "RadioButton.background", new ColorUIResource(light), + "RadioButton.border", BasicBorders.getRadioButtonBorder(), + "RadioButton.darkShadow", new ColorUIResource(shadow), + "RadioButton.focusInputMap", new UIDefaults.LazyInputMap(new Object[] { + KeyStroke.getKeyStroke("SPACE"), "pressed", + KeyStroke.getKeyStroke("released SPACE"), "released" + }), + "RadioButton.font", new FontUIResource("Dialog", Font.PLAIN, 12), + "RadioButton.foreground", new ColorUIResource(darkShadow), + "RadioButton.highlight", new ColorUIResource(highLight), + "RadioButton.icon", + new UIDefaults.LazyValue() + { + public Object createValue(UIDefaults def) + { + return BasicIconFactory.getRadioButtonIcon(); + } + }, + "RadioButton.light", new ColorUIResource(highLight), + "RadioButton.margin", new InsetsUIResource(2, 2, 2, 2), + "RadioButton.shadow", new ColorUIResource(shadow), + "RadioButton.textIconGap", new Integer(4), + "RadioButton.textShiftOffset", new Integer(0), + "RadioButtonMenuItem.acceleratorFont", + new FontUIResource("Dialog", Font.PLAIN, 12), + "RadioButtonMenuItem.acceleratorForeground", + new ColorUIResource(darkShadow), + "RadioButtonMenuItem.acceleratorSelectionForeground", + new ColorUIResource(Color.white), + "RadioButtonMenuItem.arrowIcon", BasicIconFactory.getMenuItemArrowIcon(), + "RadioButtonMenuItem.background", new ColorUIResource(light), + "RadioButtonMenuItem.border", new BasicBorders.MarginBorder(), + "RadioButtonMenuItem.borderPainted", Boolean.FALSE, + "RadioButtonMenuItem.checkIcon", BasicIconFactory.getRadioButtonMenuItemIcon(), + "RadioButtonMenuItem.font", new FontUIResource("Dialog", Font.PLAIN, 12), + "RadioButtonMenuItem.foreground", new ColorUIResource(darkShadow), + "RadioButtonMenuItem.margin", new InsetsUIResource(2, 2, 2, 2), + "RadioButtonMenuItem.selectionBackground", + new ColorUIResource(Color.black), + "RadioButtonMenuItem.selectionForeground", + new ColorUIResource(Color.white), + "RootPane.defaultButtonWindowKeyBindings", new Object[] { + "ENTER", "press", + "released ENTER", "release", + "ctrl ENTER", "press", + "ctrl released ENTER", "release" + }, + "ScrollBar.background", new ColorUIResource(224, 224, 224), + "ScrollBar.focusInputMap", new UIDefaults.LazyInputMap(new Object[] { + "PAGE_UP", "negativeBlockIncrement", + "PAGE_DOWN", "positiveBlockIncrement", + "END", "maxScroll", + "HOME", "minScroll", + "LEFT", "negativeUnitIncrement", + "KP_UP", "negativeUnitIncrement", + "KP_DOWN", "positiveUnitIncrement", + "UP", "negativeUnitIncrement", + "RIGHT", "positiveUnitIncrement", + "KP_LEFT", "negativeUnitIncrement", + "DOWN", "positiveUnitIncrement", + "KP_RIGHT", "positiveUnitIncrement" + }), + "ScrollBar.foreground", new ColorUIResource(light), + "ScrollBar.maximumThumbSize", new DimensionUIResource(4096, 4096), + "ScrollBar.minimumThumbSize", new DimensionUIResource(8, 8), + "ScrollBar.thumb", new ColorUIResource(light), + "ScrollBar.thumbDarkShadow", new ColorUIResource(shadow), + "ScrollBar.thumbHighlight", new ColorUIResource(highLight), + "ScrollBar.thumbShadow", new ColorUIResource(shadow), + "ScrollBar.track", new ColorUIResource(light), + "ScrollBar.trackHighlight", new ColorUIResource(shadow), + "ScrollBar.width", new Integer(16), + "ScrollPane.ancestorInputMap", new UIDefaults.LazyInputMap(new Object[] { + "PAGE_UP", "scrollUp", + "KP_LEFT", "unitScrollLeft", + "ctrl PAGE_DOWN", "scrollRight", + "PAGE_DOWN", "scrollDown", + "KP_RIGHT", "unitScrollRight", + "LEFT", "unitScrollLeft", + "ctrl END", "scrollEnd", + "UP", "unitScrollUp", + "RIGHT", "unitScrollRight", + "DOWN", "unitScrollDown", + "ctrl HOME", "scrollHome", + "ctrl PAGE_UP", "scrollLeft", + "KP_UP", "unitScrollUp", + "KP_DOWN", "unitScrollDown" + }), + "ScrollPane.background", new ColorUIResource(light), + "ScrollPane.border", new BorderUIResource.EtchedBorderUIResource(), + "ScrollPane.font", new FontUIResource("Dialog", Font.PLAIN, 12), + "ScrollPane.foreground", new ColorUIResource(darkShadow), + "Separator.background", new ColorUIResource(highLight), + "Separator.foreground", new ColorUIResource(shadow), + "Separator.highlight", new ColorUIResource(highLight), + "Separator.shadow", new ColorUIResource(shadow), + "Slider.background", new ColorUIResource(light), + "Slider.focus", new ColorUIResource(shadow), + "Slider.focusInputMap", new UIDefaults.LazyInputMap(new Object[] { + "ctrl PAGE_DOWN", "negativeBlockIncrement", + "PAGE_DOWN", "negativeBlockIncrement", + "PAGE_UP", "positiveBlockIncrement", + "ctrl PAGE_UP", "positiveBlockIncrement", + "KP_RIGHT", "positiveUnitIncrement", + "DOWN", "negativeUnitIncrement", + "KP_LEFT", "negativeUnitIncrement", + "RIGHT", "positiveUnitIncrement", + "KP_DOWN", "negativeUnitIncrement", + "UP", "positiveUnitIncrement", + "KP_UP", "positiveUnitIncrement", + "LEFT", "negativeUnitIncrement", + "HOME", "minScroll", + "END", "maxScroll" + }), + "Slider.focusInsets", new InsetsUIResource(2, 2, 2, 2), + "Slider.foreground", new ColorUIResource(light), + "Slider.highlight", new ColorUIResource(highLight), + "Slider.shadow", new ColorUIResource(shadow), + "Slider.thumbHeight", new Integer(20), + "Slider.thumbWidth", new Integer(11), + "Slider.tickHeight", new Integer(12), + "Slider.horizontalSize", new Dimension(200, 21), + "Slider.verticalSize", new Dimension(21, 200), + "Slider.minimumHorizontalSize", new Dimension(36, 21), + "Slider.minimumVerticalSize", new Dimension(21, 36), + "Spinner.background", new ColorUIResource(light), + "Spinner.foreground", new ColorUIResource(light), + "Spinner.arrowButtonSize", new DimensionUIResource(16, 5), + "Spinner.editorBorderPainted", Boolean.FALSE, + "Spinner.font", new FontUIResource("MonoSpaced", Font.PLAIN, 12), + "SplitPane.ancestorInputMap", new UIDefaults.LazyInputMap(new Object[] { + "F6", "toggleFocus", + "F8", "startResize", + "END", "selectMax", + "HOME", "selectMin", + "LEFT", "negativeIncrement", + "KP_UP", "negativeIncrement", + "KP_DOWN", "positiveIncrement", + "UP", "negativeIncrement", + "RIGHT", "positiveIncrement", + "KP_LEFT", "negativeIncrement", + "DOWN", "positiveIncrement", + "KP_RIGHT", "positiveIncrement", + "shift ctrl pressed TAB", "focusOutBackward", + "ctrl pressed TAB", "focusOutForward" + }), + "SplitPane.background", new ColorUIResource(light), + "SplitPane.border", new BasicBorders.SplitPaneBorder(null, null), + "SplitPane.darkShadow", new ColorUIResource(shadow), + "SplitPane.dividerSize", new Integer(7), + "SplitPane.highlight", new ColorUIResource(highLight), + "SplitPane.shadow", new ColorUIResource(shadow), + "SplitPaneDivider.border", BasicBorders.getSplitPaneDividerBorder(), + "SplitPaneDivider.draggingColor", new ColorUIResource(Color.DARK_GRAY), + "TabbedPane.ancestorInputMap", new UIDefaults.LazyInputMap(new Object[] { + "ctrl PAGE_DOWN", "navigatePageDown", + "ctrl PAGE_UP", "navigatePageUp", + "ctrl UP", "requestFocus", + "ctrl KP_UP", "requestFocus" + }), + "TabbedPane.background", new ColorUIResource(192, 192, 192), + "TabbedPane.contentBorderInsets", new InsetsUIResource(2, 2, 3, 3), + "TabbedPane.darkShadow", new ColorUIResource(Color.black), + "TabbedPane.focus", new ColorUIResource(Color.black), + "TabbedPane.focusInputMap", new UIDefaults.LazyInputMap(new Object[] { + KeyStroke.getKeyStroke("ctrl DOWN"), "requestFocusForVisibleComponent", + KeyStroke.getKeyStroke("KP_UP"), "navigateUp", + KeyStroke.getKeyStroke("LEFT"), "navigateLeft", + KeyStroke.getKeyStroke("ctrl KP_DOWN"), "requestFocusForVisibleComponent", + KeyStroke.getKeyStroke("UP"), "navigateUp", + KeyStroke.getKeyStroke("KP_DOWN"), "navigateDown", + KeyStroke.getKeyStroke("KP_LEFT"), "navigateLeft", + KeyStroke.getKeyStroke("RIGHT"), "navigateRight", + KeyStroke.getKeyStroke("KP_RIGHT"), "navigateRight", + KeyStroke.getKeyStroke("DOWN"), "navigateDown" + }), + "TabbedPane.font", new FontUIResource("Dialog", Font.PLAIN, 12), + "TabbedPane.foreground", new ColorUIResource(Color.black), + "TabbedPane.highlight", new ColorUIResource(Color.white), + "TabbedPane.light", new ColorUIResource(192, 192, 192), + "TabbedPane.selectedTabPadInsets", new InsetsUIResource(2, 2, 2, 1), + "TabbedPane.shadow", new ColorUIResource(128, 128, 128), + "TabbedPane.tabsOpaque", Boolean.TRUE, + "TabbedPane.tabAreaInsets", new InsetsUIResource(3, 2, 0, 2), + "TabbedPane.tabInsets", new InsetsUIResource(0, 4, 1, 4), + "TabbedPane.tabRunOverlay", new Integer(2), + "TabbedPane.tabsOverlapBorder", Boolean.FALSE, + "TabbedPane.textIconGap", new Integer(4), + "Table.ancestorInputMap", new UIDefaults.LazyInputMap(new Object[] { + "ctrl DOWN", "selectNextRowChangeLead", + "ctrl RIGHT", "selectNextColumnChangeLead", + "ctrl UP", "selectPreviousRowChangeLead", + "ctrl LEFT", "selectPreviousColumnChangeLead", + "CUT", "cut", + "SPACE", "addToSelection", + "ctrl SPACE", "toggleAndAnchor", + "shift SPACE", "extendTo", + "shift ctrl SPACE", "moveSelectionTo", + "ctrl X", "cut", + "ctrl C", "copy", + "ctrl KP_RIGHT", "selectNextColumnChangeLead", + "ctrl KP_LEFT", "selectPreviousColumnChangeLead", + "ctrl V", "paste", + "ctrl KP_DOWN", "selectNextRowChangeLead", + "COPY", "copy", + "ctrl KP_UP", "selectPreviousRowChangeLead", + "PASTE", "paste", + "shift PAGE_DOWN", "scrollDownExtendSelection", + "PAGE_DOWN", "scrollDownChangeSelection", + "END", "selectLastColumn", + "shift END", "selectLastColumnExtendSelection", + "HOME", "selectFirstColumn", + "ctrl END", "selectLastRow", + "ctrl shift END", "selectLastRowExtendSelection", + "LEFT", "selectPreviousColumn", + "shift HOME", "selectFirstColumnExtendSelection", + "UP", "selectPreviousRow", + "RIGHT", "selectNextColumn", + "ctrl HOME", "selectFirstRow", + "shift LEFT", "selectPreviousColumnExtendSelection", + "DOWN", "selectNextRow", + "ctrl shift HOME", "selectFirstRowExtendSelection", + "shift UP", "selectPreviousRowExtendSelection", + "F2", "startEditing", + "shift RIGHT", "selectNextColumnExtendSelection", + "TAB", "selectNextColumnCell", + "shift DOWN", "selectNextRowExtendSelection", + "ENTER", "selectNextRowCell", + "KP_UP", "selectPreviousRow", + "KP_DOWN", "selectNextRow", + "KP_LEFT", "selectPreviousColumn", + "KP_RIGHT", "selectNextColumn", + "shift TAB", "selectPreviousColumnCell", + "ctrl A", "selectAll", + "shift ENTER", "selectPreviousRowCell", + "shift KP_DOWN", "selectNextRowExtendSelection", + "shift KP_LEFT", "selectPreviousColumnExtendSelection", + "ESCAPE", "cancel", + "ctrl shift PAGE_UP", "scrollLeftExtendSelection", + "shift KP_RIGHT", "selectNextColumnExtendSelection", + "ctrl PAGE_UP", "scrollLeftChangeSelection", + "shift PAGE_UP", "scrollUpExtendSelection", + "ctrl shift PAGE_DOWN", "scrollRightExtendSelection", + "ctrl PAGE_DOWN", "scrollRightChangeSelection", + "PAGE_UP", "scrollUpChangeSelection", + "ctrl shift LEFT", "selectPreviousColumnExtendSelection", + "shift KP_UP", "selectPreviousRowExtendSelection", + "ctrl shift UP", "selectPreviousRowExtendSelection", + "ctrl shift RIGHT", "selectNextColumnExtendSelection", + "ctrl shift KP_RIGHT", "selectNextColumnExtendSelection", + "ctrl shift DOWN", "selectNextRowExtendSelection", + "ctrl BACK_SLASH", "clearSelection", + "ctrl shift KP_UP", "selectPreviousRowExtendSelection", + "ctrl shift KP_LEFT", "selectPreviousColumnExtendSelection", + "ctrl SLASH", "selectAll", + "ctrl shift KP_DOWN", "selectNextRowExtendSelection", + }), + "Table.background", new ColorUIResource(new ColorUIResource(255, 255, 255)), + "Table.focusCellBackground", new ColorUIResource(new ColorUIResource(255, 255, 255)), + "Table.focusCellForeground", new ColorUIResource(new ColorUIResource(0, 0, 0)), + "Table.focusCellHighlightBorder", + new BorderUIResource.LineBorderUIResource( + new ColorUIResource(255, 255, 0)), + "Table.font", new FontUIResource("Dialog", Font.PLAIN, 12), + "Table.foreground", new ColorUIResource(new ColorUIResource(0, 0, 0)), + "Table.gridColor", new ColorUIResource(new ColorUIResource(128, 128, 128)), + "Table.scrollPaneBorder", new BorderUIResource.BevelBorderUIResource(0), + "Table.selectionBackground", new ColorUIResource(new ColorUIResource(0, 0, 128)), + "Table.selectionForeground", new ColorUIResource(new ColorUIResource(255, 255, 255)), + "TableHeader.background", new ColorUIResource(new ColorUIResource(192, 192, 192)), + "TableHeader.font", new FontUIResource("Dialog", Font.PLAIN, 12), + "TableHeader.foreground", new ColorUIResource(new ColorUIResource(0, 0, 0)), + + "TextArea.background", new ColorUIResource(light), + "TextArea.border", new BorderUIResource(BasicBorders.getMarginBorder()), + "TextArea.caretBlinkRate", new Integer(500), + "TextArea.caretForeground", new ColorUIResource(Color.black), + "TextArea.font", new FontUIResource("MonoSpaced", Font.PLAIN, 12), + "TextArea.foreground", new ColorUIResource(Color.black), + "TextArea.inactiveForeground", new ColorUIResource(Color.gray), + "TextArea.focusInputMap", new UIDefaults.LazyInputMap(new Object[] { + KeyStroke.getKeyStroke("shift UP"), "selection-up", + KeyStroke.getKeyStroke("ctrl RIGHT"), "caret-next-word", + KeyStroke.getKeyStroke("shift ctrl LEFT"), "selection-previous-word", + KeyStroke.getKeyStroke("shift KP_UP"), "selection-up", + KeyStroke.getKeyStroke("DOWN"), "caret-down", + KeyStroke.getKeyStroke("shift ctrl T"), "previous-link-action", + KeyStroke.getKeyStroke("ctrl LEFT"), "caret-previous-word", + KeyStroke.getKeyStroke("CUT"), "cut-to-clipboard", + KeyStroke.getKeyStroke("END"), "caret-end-line", + KeyStroke.getKeyStroke("shift PAGE_UP"), "selection-page-up", + KeyStroke.getKeyStroke("KP_UP"), "caret-up", + KeyStroke.getKeyStroke("DELETE"), "delete-next", + KeyStroke.getKeyStroke("ctrl HOME"), "caret-begin", + KeyStroke.getKeyStroke("shift LEFT"), "selection-backward", + KeyStroke.getKeyStroke("ctrl END"), "caret-end", + KeyStroke.getKeyStroke("BACK_SPACE"), "delete-previous", + KeyStroke.getKeyStroke("shift ctrl RIGHT"), "selection-next-word", + KeyStroke.getKeyStroke("LEFT"), "caret-backward", + KeyStroke.getKeyStroke("KP_LEFT"), "caret-backward", + KeyStroke.getKeyStroke("shift KP_RIGHT"), "selection-forward", + KeyStroke.getKeyStroke("ctrl SPACE"), "activate-link-action", + KeyStroke.getKeyStroke("ctrl H"), "delete-previous", + KeyStroke.getKeyStroke("ctrl BACK_SLASH"), "unselect", + KeyStroke.getKeyStroke("ENTER"), "insert-break", + KeyStroke.getKeyStroke("shift HOME"), "selection-begin-line", + KeyStroke.getKeyStroke("RIGHT"), "caret-forward", + KeyStroke.getKeyStroke("shift ctrl PAGE_UP"), "selection-page-left", + KeyStroke.getKeyStroke("shift DOWN"), "selection-down", + KeyStroke.getKeyStroke("PAGE_DOWN"), "page-down", + KeyStroke.getKeyStroke("shift KP_LEFT"), "selection-backward", + KeyStroke.getKeyStroke("shift ctrl O"), "toggle-componentOrientation", + KeyStroke.getKeyStroke("ctrl X"), "cut-to-clipboard", + KeyStroke.getKeyStroke("shift ctrl PAGE_DOWN"), "selection-page-right", + KeyStroke.getKeyStroke("ctrl C"), "copy-to-clipboard", + KeyStroke.getKeyStroke("ctrl KP_RIGHT"), "caret-next-word", + KeyStroke.getKeyStroke("shift END"), "selection-end-line", + KeyStroke.getKeyStroke("ctrl KP_LEFT"), "caret-previous-word", + KeyStroke.getKeyStroke("HOME"), "caret-begin-line", + KeyStroke.getKeyStroke("ctrl V"), "paste-from-clipboard", + KeyStroke.getKeyStroke("KP_DOWN"), "caret-down", + KeyStroke.getKeyStroke("ctrl A"), "select-all", + KeyStroke.getKeyStroke("shift RIGHT"), "selection-forward", + KeyStroke.getKeyStroke("shift ctrl END"), "selection-end", + KeyStroke.getKeyStroke("COPY"), "copy-to-clipboard", + KeyStroke.getKeyStroke("shift ctrl KP_LEFT"), "selection-previous-word", + KeyStroke.getKeyStroke("ctrl T"), "next-link-action", + KeyStroke.getKeyStroke("shift KP_DOWN"), "selection-down", + KeyStroke.getKeyStroke("TAB"), "insert-tab", + KeyStroke.getKeyStroke("UP"), "caret-up", + KeyStroke.getKeyStroke("shift ctrl HOME"), "selection-begin", + KeyStroke.getKeyStroke("shift PAGE_DOWN"), "selection-page-down", + KeyStroke.getKeyStroke("KP_RIGHT"), "caret-forward", + KeyStroke.getKeyStroke("shift ctrl KP_RIGHT"), "selection-next-word", + KeyStroke.getKeyStroke("PAGE_UP"), "page-up", + KeyStroke.getKeyStroke("PASTE"), "paste-from-clipboard" + }), + "TextArea.margin", new InsetsUIResource(0, 0, 0, 0), + "TextArea.selectionBackground", new ColorUIResource(Color.black), + "TextArea.selectionForeground", new ColorUIResource(Color.white), + "TextField.background", new ColorUIResource(light), + "TextField.border", new BasicBorders.FieldBorder(null, null, null, null), + "TextField.caretBlinkRate", new Integer(500), + "TextField.caretForeground", new ColorUIResource(Color.black), + "TextField.darkShadow", new ColorUIResource(shadow), + "TextField.font", new FontUIResource("SansSerif", Font.PLAIN, 12), + "TextField.foreground", new ColorUIResource(Color.black), + "TextField.highlight", new ColorUIResource(highLight), + "TextField.inactiveBackground", new ColorUIResource(Color.LIGHT_GRAY), + "TextField.inactiveForeground", new ColorUIResource(Color.GRAY), + "TextField.light", new ColorUIResource(highLight), + "TextField.highlight", new ColorUIResource(light), + "TextField.focusInputMap", new UIDefaults.LazyInputMap(new Object[] { + KeyStroke.getKeyStroke("ENTER"), "notify-field-accept", + KeyStroke.getKeyStroke("LEFT"), "caret-backward", + KeyStroke.getKeyStroke("RIGHT"), "caret-forward", + KeyStroke.getKeyStroke("BACK_SPACE"), "delete-previous", + KeyStroke.getKeyStroke("ctrl X"), "cut-to-clipboard", + KeyStroke.getKeyStroke("ctrl C"), "copy-to-clipboard", + KeyStroke.getKeyStroke("ctrl V"), "paste-from-clipboard", + KeyStroke.getKeyStroke("shift LEFT"), "selection-backward", + KeyStroke.getKeyStroke("shift RIGHT"), "selection-forward", + KeyStroke.getKeyStroke("HOME"), "caret-begin-line", + KeyStroke.getKeyStroke("END"), "caret-end-line", + KeyStroke.getKeyStroke("DELETE"), "delete-next", + KeyStroke.getKeyStroke("shift ctrl O"), "toggle-componentOrientation", + KeyStroke.getKeyStroke("shift KP_LEFT"), "selection-backward", + KeyStroke.getKeyStroke("ctrl H"), "delete-previous", + KeyStroke.getKeyStroke("KP_LEFT"), "caret-backward", + KeyStroke.getKeyStroke("KP_RIGHT"), "caret-forward", + KeyStroke.getKeyStroke("shift ctrl KP_RIGHT"), "selection-next-word", + KeyStroke.getKeyStroke("COPY"), "copy-to-clipboard", + KeyStroke.getKeyStroke("shift HOME"), "selection-begin-line", + KeyStroke.getKeyStroke("shift ctrl LEFT"), "selection-previous-word", + KeyStroke.getKeyStroke("ctrl KP_LEFT"), "caret-previous-word", + KeyStroke.getKeyStroke("ctrl KP_RIGHT"), "caret-next-word", + KeyStroke.getKeyStroke("PASTE"), "paste-from-clipboard", + KeyStroke.getKeyStroke("shift ctrl RIGHT"), "selection-next-word", + KeyStroke.getKeyStroke("ctrl BACK_SLASH"), "unselect", + KeyStroke.getKeyStroke("ctrl A"), "select-all", + KeyStroke.getKeyStroke("shift KP_RIGHT"), "selection-forward", + KeyStroke.getKeyStroke("CUT"), "cut-to-clipboard", + KeyStroke.getKeyStroke("ctrl LEFT"), "caret-previous-word", + KeyStroke.getKeyStroke("shift ctrl KP_LEFT"), "selection-previous-word", + KeyStroke.getKeyStroke("shift END"), "selection-end-line", + KeyStroke.getKeyStroke("ctrl RIGHT"), "caret-next-word" + }), + "TextField.margin", new InsetsUIResource(0, 0, 0, 0), + "TextField.selectionBackground", new ColorUIResource(Color.black), + "TextField.selectionForeground", new ColorUIResource(Color.white), + "TextPane.background", new ColorUIResource(Color.white), + "TextPane.border", BasicBorders.getMarginBorder(), + "TextPane.caretBlinkRate", new Integer(500), + "TextPane.caretForeground", new ColorUIResource(Color.black), + "TextPane.font", new FontUIResource("Serif", Font.PLAIN, 12), + "TextPane.foreground", new ColorUIResource(Color.black), + "TextPane.inactiveForeground", new ColorUIResource(Color.gray), + "TextPane.focusInputMap", new UIDefaults.LazyInputMap(new Object[] { + KeyStroke.getKeyStroke("shift UP"), "selection-up", + KeyStroke.getKeyStroke("ctrl RIGHT"), "caret-next-word", + KeyStroke.getKeyStroke("shift ctrl LEFT"), "selection-previous-word", + KeyStroke.getKeyStroke("shift KP_UP"), "selection-up", + KeyStroke.getKeyStroke("DOWN"), "caret-down", + KeyStroke.getKeyStroke("shift ctrl T"), "previous-link-action", + KeyStroke.getKeyStroke("ctrl LEFT"), "caret-previous-word", + KeyStroke.getKeyStroke("CUT"), "cut-to-clipboard", + KeyStroke.getKeyStroke("END"), "caret-end-line", + KeyStroke.getKeyStroke("shift PAGE_UP"), "selection-page-up", + KeyStroke.getKeyStroke("KP_UP"), "caret-up", + KeyStroke.getKeyStroke("DELETE"), "delete-next", + KeyStroke.getKeyStroke("ctrl HOME"), "caret-begin", + KeyStroke.getKeyStroke("shift LEFT"), "selection-backward", + KeyStroke.getKeyStroke("ctrl END"), "caret-end", + KeyStroke.getKeyStroke("BACK_SPACE"), "delete-previous", + KeyStroke.getKeyStroke("shift ctrl RIGHT"), "selection-next-word", + KeyStroke.getKeyStroke("LEFT"), "caret-backward", + KeyStroke.getKeyStroke("KP_LEFT"), "caret-backward", + KeyStroke.getKeyStroke("shift KP_RIGHT"), "selection-forward", + KeyStroke.getKeyStroke("ctrl SPACE"), "activate-link-action", + KeyStroke.getKeyStroke("ctrl H"), "delete-previous", + KeyStroke.getKeyStroke("ctrl BACK_SLASH"), "unselect", + KeyStroke.getKeyStroke("ENTER"), "insert-break", + KeyStroke.getKeyStroke("shift HOME"), "selection-begin-line", + KeyStroke.getKeyStroke("RIGHT"), "caret-forward", + KeyStroke.getKeyStroke("shift ctrl PAGE_UP"), "selection-page-left", + KeyStroke.getKeyStroke("shift DOWN"), "selection-down", + KeyStroke.getKeyStroke("PAGE_DOWN"), "page-down", + KeyStroke.getKeyStroke("shift KP_LEFT"), "selection-backward", + KeyStroke.getKeyStroke("shift ctrl O"), "toggle-componentOrientation", + KeyStroke.getKeyStroke("ctrl X"), "cut-to-clipboard", + KeyStroke.getKeyStroke("shift ctrl PAGE_DOWN"), "selection-page-right", + KeyStroke.getKeyStroke("ctrl C"), "copy-to-clipboard", + KeyStroke.getKeyStroke("ctrl KP_RIGHT"), "caret-next-word", + KeyStroke.getKeyStroke("shift END"), "selection-end-line", + KeyStroke.getKeyStroke("ctrl KP_LEFT"), "caret-previous-word", + KeyStroke.getKeyStroke("HOME"), "caret-begin-line", + KeyStroke.getKeyStroke("ctrl V"), "paste-from-clipboard", + KeyStroke.getKeyStroke("KP_DOWN"), "caret-down", + KeyStroke.getKeyStroke("ctrl A"), "select-all", + KeyStroke.getKeyStroke("shift RIGHT"), "selection-forward", + KeyStroke.getKeyStroke("shift ctrl END"), "selection-end", + KeyStroke.getKeyStroke("COPY"), "copy-to-clipboard", + KeyStroke.getKeyStroke("shift ctrl KP_LEFT"), "selection-previous-word", + KeyStroke.getKeyStroke("ctrl T"), "next-link-action", + KeyStroke.getKeyStroke("shift KP_DOWN"), "selection-down", + KeyStroke.getKeyStroke("TAB"), "insert-tab", + KeyStroke.getKeyStroke("UP"), "caret-up", + KeyStroke.getKeyStroke("shift ctrl HOME"), "selection-begin", + KeyStroke.getKeyStroke("shift PAGE_DOWN"), "selection-page-down", + KeyStroke.getKeyStroke("KP_RIGHT"), "caret-forward", + KeyStroke.getKeyStroke("shift ctrl KP_RIGHT"), "selection-next-word", + KeyStroke.getKeyStroke("PAGE_UP"), "page-up", + KeyStroke.getKeyStroke("PASTE"), "paste-from-clipboard" + }), + "TextPane.margin", new InsetsUIResource(3, 3, 3, 3), + "TextPane.selectionBackground", new ColorUIResource(Color.black), + "TextPane.selectionForeground", new ColorUIResource(Color.white), + "TitledBorder.border", new BorderUIResource.EtchedBorderUIResource(), + "TitledBorder.font", new FontUIResource("Dialog", Font.PLAIN, 12), + "TitledBorder.titleColor", new ColorUIResource(darkShadow), + "ToggleButton.background", new ColorUIResource(light), + "ToggleButton.border", + new BorderUIResource.CompoundBorderUIResource(null, null), + "ToggleButton.darkShadow", new ColorUIResource(shadow), + "ToggleButton.focusInputMap", new UIDefaults.LazyInputMap(new Object[] { + KeyStroke.getKeyStroke("SPACE"), "pressed", + KeyStroke.getKeyStroke("released SPACE"), "released" + }), + "ToggleButton.font", new FontUIResource("Dialog", Font.PLAIN, 12), + "ToggleButton.foreground", new ColorUIResource(darkShadow), + "ToggleButton.highlight", new ColorUIResource(highLight), + "ToggleButton.light", new ColorUIResource(light), + "ToggleButton.margin", new InsetsUIResource(2, 14, 2, 14), + "ToggleButton.shadow", new ColorUIResource(shadow), + "ToggleButton.textIconGap", new Integer(4), + "ToggleButton.textShiftOffset", new Integer(0), + "ToolBar.ancestorInputMap", new UIDefaults.LazyInputMap(new Object[] { + "UP", "navigateUp", + "KP_UP", "navigateUp", + "DOWN", "navigateDown", + "KP_DOWN", "navigateDown", + "LEFT", "navigateLeft", + "KP_LEFT", "navigateLeft", + "RIGHT", "navigateRight", + "KP_RIGHT", "navigateRight" + }), + "ToolBar.background", new ColorUIResource(light), + "ToolBar.border", new BorderUIResource.EtchedBorderUIResource(), + "ToolBar.darkShadow", new ColorUIResource(shadow), + "ToolBar.dockingBackground", new ColorUIResource(light), + "ToolBar.dockingForeground", new ColorUIResource(Color.red), + "ToolBar.floatingBackground", new ColorUIResource(light), + "ToolBar.floatingForeground", new ColorUIResource(Color.darkGray), + "ToolBar.font", new FontUIResource("Dialog", Font.PLAIN, 12), + "ToolBar.foreground", new ColorUIResource(darkShadow), + "ToolBar.highlight", new ColorUIResource(highLight), + "ToolBar.light", new ColorUIResource(highLight), + "ToolBar.separatorSize", new DimensionUIResource(10, 10), + "ToolBar.shadow", new ColorUIResource(shadow), + "ToolTip.background", new ColorUIResource(light), + "ToolTip.border", new BorderUIResource.LineBorderUIResource(Color.lightGray), + "ToolTip.font", new FontUIResource("SansSerif", Font.PLAIN, 12), + "ToolTip.foreground", new ColorUIResource(darkShadow), + "Tree.ancestorInputMap", new UIDefaults.LazyInputMap(new Object[] { + "ESCAPE", "cancel" + }), + "Tree.background", new ColorUIResource(new Color(255, 255, 255)), + "Tree.changeSelectionWithFocus", Boolean.TRUE, + "Tree.drawsFocusBorderAroundIcon", Boolean.FALSE, + "Tree.editorBorder", new BorderUIResource.LineBorderUIResource(Color.lightGray), + "Tree.focusInputMap", new UIDefaults.LazyInputMap(new Object[] { + KeyStroke.getKeyStroke("ctrl DOWN"), "selectNextChangeLead", + KeyStroke.getKeyStroke("shift UP"), "selectPreviousExtendSelection", + KeyStroke.getKeyStroke("ctrl RIGHT"), "scrollRight", + KeyStroke.getKeyStroke("shift KP_UP"), "selectPreviousExtendSelection", + KeyStroke.getKeyStroke("DOWN"), "selectNext", + KeyStroke.getKeyStroke("ctrl UP"), "selectPreviousChangeLead", + KeyStroke.getKeyStroke("ctrl LEFT"), "scrollLeft", + KeyStroke.getKeyStroke("CUT"), "cut", + KeyStroke.getKeyStroke("END"), "selectLast", + KeyStroke.getKeyStroke("shift PAGE_UP"), "scrollUpExtendSelection", + KeyStroke.getKeyStroke("KP_UP"), "selectPrevious", + KeyStroke.getKeyStroke("shift ctrl UP"), "selectPreviousExtendSelection", + KeyStroke.getKeyStroke("ctrl HOME"), "selectFirstChangeLead", + KeyStroke.getKeyStroke("ctrl END"), "selectLastChangeLead", + KeyStroke.getKeyStroke("ctrl PAGE_DOWN"), "scrollDownChangeLead", + KeyStroke.getKeyStroke("LEFT"), "selectParent", + KeyStroke.getKeyStroke("ctrl PAGE_UP"), "scrollUpChangeLead", + KeyStroke.getKeyStroke("KP_LEFT"), "selectParent", + KeyStroke.getKeyStroke("SPACE"), "addToSelection", + KeyStroke.getKeyStroke("ctrl SPACE"), "toggleAndAnchor", + KeyStroke.getKeyStroke("shift SPACE"), "extendTo", + KeyStroke.getKeyStroke("shift ctrl SPACE"), "moveSelectionTo", + KeyStroke.getKeyStroke("ADD"), "expand", + KeyStroke.getKeyStroke("ctrl BACK_SLASH"), "clearSelection", + KeyStroke.getKeyStroke("shift ctrl DOWN"), "selectNextExtendSelection", + KeyStroke.getKeyStroke("shift HOME"), "selectFirstExtendSelection", + KeyStroke.getKeyStroke("RIGHT"), "selectChild", + KeyStroke.getKeyStroke("shift ctrl PAGE_UP"), "scrollUpExtendSelection", + KeyStroke.getKeyStroke("shift DOWN"), "selectNextExtendSelection", + KeyStroke.getKeyStroke("PAGE_DOWN"), "scrollDownChangeSelection", + KeyStroke.getKeyStroke("shift ctrl KP_UP"), "selectPreviousExtendSelection", + KeyStroke.getKeyStroke("SUBTRACT"), "collapse", + KeyStroke.getKeyStroke("ctrl X"), "cut", + KeyStroke.getKeyStroke("shift ctrl PAGE_DOWN"), "scrollDownExtendSelection", + KeyStroke.getKeyStroke("ctrl SLASH"), "selectAll", + KeyStroke.getKeyStroke("ctrl C"), "copy", + KeyStroke.getKeyStroke("ctrl KP_RIGHT"), "scrollRight", + KeyStroke.getKeyStroke("shift END"), "selectLastExtendSelection", + KeyStroke.getKeyStroke("shift ctrl KP_DOWN"), "selectNextExtendSelection", + KeyStroke.getKeyStroke("ctrl KP_LEFT"), "scrollLeft", + KeyStroke.getKeyStroke("HOME"), "selectFirst", + KeyStroke.getKeyStroke("ctrl V"), "paste", + KeyStroke.getKeyStroke("KP_DOWN"), "selectNext", + KeyStroke.getKeyStroke("ctrl A"), "selectAll", + KeyStroke.getKeyStroke("ctrl KP_DOWN"), "selectNextChangeLead", + KeyStroke.getKeyStroke("shift ctrl END"), "selectLastExtendSelection", + KeyStroke.getKeyStroke("COPY"), "copy", + KeyStroke.getKeyStroke("ctrl KP_UP"), "selectPreviousChangeLead", + KeyStroke.getKeyStroke("shift KP_DOWN"), "selectNextExtendSelection", + KeyStroke.getKeyStroke("UP"), "selectPrevious", + KeyStroke.getKeyStroke("shift ctrl HOME"), "selectFirstExtendSelection", + KeyStroke.getKeyStroke("shift PAGE_DOWN"), "scrollDownExtendSelection", + KeyStroke.getKeyStroke("KP_RIGHT"), "selectChild", + KeyStroke.getKeyStroke("F2"), "startEditing", + KeyStroke.getKeyStroke("PAGE_UP"), "scrollUpChangeSelection", + KeyStroke.getKeyStroke("PASTE"), "paste" + }), + "Tree.font", new FontUIResource("Dialog", Font.PLAIN, 12), + "Tree.foreground", new ColorUIResource(Color.black), + "Tree.hash", new ColorUIResource(new Color(184, 207, 228)), + "Tree.leftChildIndent", new Integer(7), + "Tree.rightChildIndent", new Integer(13), + "Tree.rowHeight", new Integer(16), + "Tree.scrollsOnExpand", Boolean.TRUE, + "Tree.selectionBackground", new ColorUIResource(Color.black), + "Tree.nonSelectionBackground", new ColorUIResource(new Color(255, 255, 255)), + "Tree.selectionBorderColor", new ColorUIResource(Color.black), + "Tree.selectionBorder", new BorderUIResource.LineBorderUIResource(Color.black), + "Tree.selectionForeground", new ColorUIResource(new Color(255, 255, 255)), + "Viewport.background", new ColorUIResource(light), + "Viewport.foreground", new ColorUIResource(Color.black), + "Viewport.font", new FontUIResource("Dialog", Font.PLAIN, 12) + }; + defaults.putDefaults(uiDefaults); + } + + /** + * Returns the <code>ActionMap</code> that stores all the actions that are + * responsibly for rendering auditory cues. + * + * @return the action map that stores all the actions that are + * responsibly for rendering auditory cues + * + * @see #createAudioAction + * @see #playSound + * + * @since 1.4 + */ + protected ActionMap getAudioActionMap() + { + if (audioActionMap != null) + audioActionMap = new ActionMap(); + return audioActionMap; + } + + /** + * Creates an <code>Action</code> that can play an auditory cue specified by + * the key. The UIDefaults value for the key is normally a String that points + * to an audio file relative to the current package. + * + * @param key a UIDefaults key that specifies the sound + * + * @return an action that can play the sound + * + * @see #playSound + * + * @since 1.4 + */ + protected Action createAudioAction(Object key) + { + return new AudioAction(key); + } + + /** + * Plays the sound of the action if it is listed in + * <code>AuditoryCues.playList</code>. + * + * @param audioAction the audio action to play + * + * @since 1.4 + */ + protected void playSound(Action audioAction) + { + if (audioAction instanceof AudioAction) + { + Object[] playList = (Object[]) UIManager.get("AuditoryCues.playList"); + for (int i = 0; i < playList.length; ++i) + { + if (playList[i].equals(((AudioAction) audioAction).key)) + { + ActionEvent ev = new ActionEvent(this, + ActionEvent.ACTION_PERFORMED, + (String) playList[i]); + audioAction.actionPerformed(ev); + break; + } + } + } + } + + /** + * Initializes the Look and Feel. + */ + public void initialize() + { + Toolkit toolkit = Toolkit.getDefaultToolkit(); + popupHelper = new PopupHelper(); + toolkit.addAWTEventListener(popupHelper, AWTEvent.MOUSE_EVENT_MASK); + } + + /** + * Uninitializes the Look and Feel. + */ + public void uninitialize() + { + Toolkit toolkit = Toolkit.getDefaultToolkit(); + toolkit.removeAWTEventListener(popupHelper); + popupHelper = null; + } +} diff --git a/libjava/classpath/javax/swing/plaf/basic/BasicMenuBarUI.java b/libjava/classpath/javax/swing/plaf/basic/BasicMenuBarUI.java new file mode 100644 index 000000000..b9e29128a --- /dev/null +++ b/libjava/classpath/javax/swing/plaf/basic/BasicMenuBarUI.java @@ -0,0 +1,481 @@ +/* BasicMenuBarUI.java -- + Copyright (C) 2002, 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 java.awt.Dimension; +import java.awt.event.ActionEvent; +import java.awt.event.ContainerEvent; +import java.awt.event.ContainerListener; +import java.awt.event.MouseEvent; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; + +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.ActionMap; +import javax.swing.BoxLayout; +import javax.swing.InputMap; +import javax.swing.JComponent; +import javax.swing.JMenu; +import javax.swing.JMenuBar; +import javax.swing.LookAndFeel; +import javax.swing.MenuElement; +import javax.swing.MenuSelectionManager; +import javax.swing.SwingUtilities; +import javax.swing.UIManager; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import javax.swing.event.MouseInputListener; +import javax.swing.plaf.ActionMapUIResource; +import javax.swing.plaf.ComponentUI; +import javax.swing.plaf.MenuBarUI; + +/** + * UI Delegate for JMenuBar. + */ +public class BasicMenuBarUI extends MenuBarUI +{ + + /** + * This action is performed for the action command 'takeFocus'. + */ + private static class FocusAction + extends AbstractAction + { + + /** + * Creates a new FocusAction. + */ + FocusAction() + { + super("takeFocus"); + } + + /** + * Performs the action. + */ + public void actionPerformed(ActionEvent event) + { + // In the JDK this action seems to pop up the first menu of the + // menu bar. + JMenuBar menuBar = (JMenuBar) event.getSource(); + MenuSelectionManager defaultManager = + MenuSelectionManager.defaultManager(); + MenuElement me[]; + MenuElement subElements[]; + JMenu menu = menuBar.getMenu(0); + if (menu != null) + { + me = new MenuElement[3]; + me[0] = (MenuElement) menuBar; + me[1] = (MenuElement) menu; + me[2] = (MenuElement) menu.getPopupMenu(); + defaultManager.setSelectedPath(me); + } + } + + } + + protected ChangeListener changeListener; + + /*ContainerListener that listens to the ContainerEvents fired from menu bar*/ + protected ContainerListener containerListener; + + /*Property change listeners that listener to PropertyChangeEvent from menu bar*/ + private PropertyChangeListener propertyChangeListener; + + /* menu bar for which this UI delegate is for*/ + protected JMenuBar menuBar; + + /* MouseListener that listens to the mouseEvents fired from menu bar*/ + private MouseInputListener mouseListener; + + /** + * Creates a new BasicMenuBarUI object. + */ + public BasicMenuBarUI() + { + changeListener = createChangeListener(); + containerListener = createContainerListener(); + propertyChangeListener = new PropertyChangeHandler(); + mouseListener = new MouseInputHandler(); + } + + /** + * Creates ChangeListener + * + * @return The ChangeListener + */ + protected ChangeListener createChangeListener() + { + return new ChangeHandler(); + } + + /** + * Creates ContainerListener() to listen for ContainerEvents + * fired by JMenuBar. + * + * @return The ContainerListener + */ + protected ContainerListener createContainerListener() + { + return new ContainerHandler(); + } + + /** + * Factory method to create a BasicMenuBarUI for the given {@link + * JComponent}, which should be a {@link JMenuBar}. + * + * @param x The {@link JComponent} a UI is being created for. + * + * @return A BasicMenuBarUI for the {@link JComponent}. + */ + public static ComponentUI createUI(JComponent x) + { + return new BasicMenuBarUI(); + } + + /** + * Returns maximum size for the specified menu bar + * + * @param c component for which to get maximum size + * + * @return Maximum size for the specified menu bar + */ + public Dimension getMaximumSize(JComponent c) + { + // let layout manager calculate its size + return null; + } + + /** + * Returns maximum allowed size of JMenuBar. + * + * @param c menuBar for which to return maximum size + * + * @return Maximum size of the give menu bar. + */ + public Dimension getMinimumSize(JComponent c) + { + // let layout manager calculate its size + return null; + } + + /** + * Returns preferred size of JMenuBar. + * + * @param c menuBar for which to return preferred size + * + * @return Preferred size of the give menu bar. + */ + public Dimension getPreferredSize(JComponent c) + { + // let layout manager calculate its size + return null; + } + + /** + * Initializes any default properties that this UI has from the defaults for + * the Basic look and feel. + */ + protected void installDefaults() + { + LookAndFeel.installBorder(menuBar, "MenuBar.border"); + LookAndFeel.installColorsAndFont(menuBar, "MenuBar.background", + "MenuBar.foreground", "MenuBar.font"); + menuBar.setOpaque(true); + } + + /** + * This method installs the keyboard actions for the JMenuBar. + */ + protected void installKeyboardActions() + { + // Install InputMap. + Object[] bindings = + (Object[]) SharedUIDefaults.get("MenuBar.windowBindings"); + InputMap inputMap = LookAndFeel.makeComponentInputMap(menuBar, bindings); + SwingUtilities.replaceUIInputMap(menuBar, + JComponent.WHEN_IN_FOCUSED_WINDOW, + inputMap); + + // Install ActionMap. + SwingUtilities.replaceUIActionMap(menuBar, getActionMap()); + } + + /** + * Creates and returns the shared action map for JTrees. + * + * @return the shared action map for JTrees + */ + private ActionMap getActionMap() + { + ActionMap am = (ActionMap) UIManager.get("MenuBar.actionMap"); + if (am == null) + { + am = createDefaultActions(); + UIManager.getLookAndFeelDefaults().put("MenuBar.actionMap", am); + } + return am; + } + + /** + * Creates the default actions when there are none specified by the L&F. + * + * @return the default actions + */ + private ActionMap createDefaultActions() + { + ActionMapUIResource am = new ActionMapUIResource(); + Action action = new FocusAction(); + am.put(action.getValue(Action.NAME), action); + return am; + } + + /** + * This method installs the listeners needed for this UI to function. + */ + protected void installListeners() + { + menuBar.addContainerListener(containerListener); + menuBar.addPropertyChangeListener(propertyChangeListener); + menuBar.addMouseListener(mouseListener); + } + + /** + * Installs and initializes all fields for this UI delegate. Any properties + * of the UI that need to be initialized and/or set to defaults will be + * done now. It will also install any listeners necessary. + * + * @param c The {@link JComponent} that is having this UI installed. + */ + public void installUI(JComponent c) + { + super.installUI(c); + menuBar = (JMenuBar) c; + menuBar.setLayout(new BoxLayout(menuBar, BoxLayout.X_AXIS)); + installDefaults(); + installListeners(); + installKeyboardActions(); + } + + /** + * This method uninstalls the defaults and nulls any objects created during + * install. + */ + protected void uninstallDefaults() + { + menuBar.setBackground(null); + menuBar.setBorder(null); + menuBar.setFont(null); + menuBar.setForeground(null); + } + + /** + * This method reverses the work done in installKeyboardActions. + */ + protected void uninstallKeyboardActions() + { + SwingUtilities.replaceUIInputMap(menuBar, + JComponent.WHEN_IN_FOCUSED_WINDOW, null); + SwingUtilities.replaceUIActionMap(menuBar, null); + } + + /** + * Unregisters all the listeners that this UI delegate was using. + */ + protected void uninstallListeners() + { + menuBar.removeContainerListener(containerListener); + menuBar.removePropertyChangeListener(propertyChangeListener); + menuBar.removeMouseListener(mouseListener); + } + + /** + * Performs the opposite of installUI. Any properties or resources that need + * to be cleaned up will be done now. It will also uninstall any listeners + * it has. In addition, any properties of this UI will be nulled. + * + * @param c The {@link JComponent} that is having this UI uninstalled. + */ + public void uninstallUI(JComponent c) + { + uninstallDefaults(); + uninstallListeners(); + uninstallKeyboardActions(); + menuBar = null; + } + + private class ChangeHandler implements ChangeListener + { + public void stateChanged(ChangeEvent event) + { + // TODO: What should be done here, if anything? + } + } + + /** + * This class handles ContainerEvents fired by JMenuBar. It revalidates + * and repaints menu bar whenever menu is added or removed from it. + */ + private class ContainerHandler implements ContainerListener + { + /** + * This method is called whenever menu is added to the menu bar + * + * @param e The ContainerEvent. + */ + public void componentAdded(ContainerEvent e) + { + menuBar.revalidate(); + menuBar.repaint(); + } + + /** + * This method is called whenever menu is removed from the menu bar. + * + * @param e The ContainerEvent. + */ + public void componentRemoved(ContainerEvent e) + { + menuBar.revalidate(); + menuBar.repaint(); + } + } + + /** + * This class handles PropertyChangeEvents fired from the JMenuBar + */ + private class PropertyChangeHandler implements PropertyChangeListener + { + /** + * This method is called whenever one of the properties of the MenuBar + * changes. + * + * @param e The PropertyChangeEvent. + */ + public void propertyChange(PropertyChangeEvent e) + { + if (e.getPropertyName().equals("borderPainted")) + menuBar.repaint(); + if (e.getPropertyName().equals("margin")) + menuBar.repaint(); + } + } + + private class MouseInputHandler implements MouseInputListener + { + /** + * Handles mouse clicked event + * + * @param e Mouse event + */ + public void mouseClicked(MouseEvent e) + { + MenuElement[] me = menuBar.getSubElements(); + + for (int i = 0; i < me.length; i++) + { + JMenu menu = menuBar.getMenu(i); + if (menu != null) + menu.setSelected(false); + } + } + + /** + * Handles mouse pressed event + * + * @param e Mouse event + */ + public void mousePressed(MouseEvent e) + { + // TODO: What should be done here, if anything? + } + + /** + * Handles mouse released event + * + * @param e Mouse event + */ + public void mouseReleased(MouseEvent e) + { + // TODO: What should be done here, if anything? + } + + /** + * Handles mouse exited event + * + * @param e Mouse event + */ + public void mouseExited(MouseEvent e) + { + // TODO: What should be done here, if anything? + } + + /** + * Handles mouse dragged event + * + * @param e Mouse event + */ + public void mouseDragged(MouseEvent e) + { + // TODO: What should be done here, if anything? + } + + /** + * Handles mouse moved event + * + * @param e Mouse event + */ + public void mouseMoved(MouseEvent e) + { + // TODO: What should be done here, if anything? + } + + /** + * Handles mouse entered event + * + * @param e Mouse event + */ + public void mouseEntered(MouseEvent e) + { + // TODO: What should be done here, if anything? + } + } +} diff --git a/libjava/classpath/javax/swing/plaf/basic/BasicMenuItemUI.java b/libjava/classpath/javax/swing/plaf/basic/BasicMenuItemUI.java new file mode 100644 index 000000000..40b539378 --- /dev/null +++ b/libjava/classpath/javax/swing/plaf/basic/BasicMenuItemUI.java @@ -0,0 +1,1339 @@ +/* BasicMenuItemUI.java -- + Copyright (C) 2002, 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.Component; +import java.awt.Container; +import java.awt.Dimension; +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; +import javax.swing.LookAndFeel; +import javax.swing.MenuElement; +import javax.swing.MenuSelectionManager; +import javax.swing.SwingConstants; +import javax.swing.SwingUtilities; +import javax.swing.UIDefaults; +import javax.swing.UIManager; +import javax.swing.event.MenuDragMouseEvent; +import javax.swing.event.MenuDragMouseListener; +import javax.swing.event.MenuKeyEvent; +import javax.swing.event.MenuKeyListener; +import javax.swing.event.MouseInputListener; +import javax.swing.plaf.ActionMapUIResource; +import javax.swing.plaf.ComponentInputMapUIResource; +import javax.swing.plaf.ComponentUI; +import javax.swing.plaf.MenuItemUI; +import javax.swing.text.View; + +/** + * UI Delegate for JMenuItem. + */ +public class BasicMenuItemUI extends MenuItemUI +{ + /** + * Font to be used when displaying menu item's accelerator. + */ + protected Font acceleratorFont; + + /** + * Color to be used when displaying menu item's accelerator. + */ + protected Color acceleratorForeground; + + /** + * Color to be used when displaying menu item's accelerator when menu item is + * selected. + */ + protected Color acceleratorSelectionForeground; + + /** + * Icon that is displayed after the text to indicated that this menu contains + * submenu. + */ + protected Icon arrowIcon; + + /** + * Icon that is displayed before the text. This icon is only used in + * JCheckBoxMenuItem or JRadioBoxMenuItem. + */ + protected Icon checkIcon; + + /** + * Number of spaces between icon and text. + */ + protected int defaultTextIconGap = 4; + + /** + * Color of the text when menu item is disabled + */ + protected Color disabledForeground; + + /** + * The menu Drag mouse listener listening to the menu item. + */ + protected MenuDragMouseListener menuDragMouseListener; + + /** + * The menu item itself + */ + protected JMenuItem menuItem; + + /** + * Menu Key listener listening to the menu item. + */ + protected MenuKeyListener menuKeyListener; + + /** + * mouse input listener listening to menu item. + */ + protected MouseInputListener mouseInputListener; + + /** + * Indicates if border should be painted + */ + protected boolean oldBorderPainted; + + /** + * Color of text that is used when menu item is selected + */ + protected Color selectionBackground; + + /** + * Color of the text that is used when menu item is selected. + */ + protected Color selectionForeground; + + /** + * String that separates description of the modifiers and the key + */ + private String acceleratorDelimiter; + + /** + * ItemListener to listen for item changes in the menu item + */ + private ItemListener itemListener; + + /** + * A PropertyChangeListener to make UI updates after property changes. + */ + private PropertyChangeHandler propertyChangeListener; + + /** + * The view rectangle used for layout of the menu item. + */ + private Rectangle viewRect; + + /** + * The rectangle that holds the area of the label. + */ + private Rectangle textRect; + + /** + * The rectangle that holds the area of the accelerator. + */ + private Rectangle accelRect; + + /** + * The rectangle that holds the area of the icon. + */ + private Rectangle iconRect; + + /** + * The rectangle that holds the area of the icon. + */ + private Rectangle arrowIconRect; + + /** + * The rectangle that holds the area of the check icon. + */ + private Rectangle checkIconRect; + + /** + * A rectangle used for temporary storage to avoid creation of new + * rectangles. + */ + private Rectangle cachedRect; + + /** + * A class to handle PropertChangeEvents for the JMenuItem + * @author Anthony Balkissoon abalkiss at redhat dot com. + */ + class PropertyChangeHandler implements PropertyChangeListener + { + /** + * 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) + { + 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")) + && SystemProperties.getProperty("gnu.javax.swing.noGraphics2D") + == null) + { + 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 + { + /** + * This is what is done when the accelerator key for the JMenuItem is + * pressed. + */ + public void actionPerformed(ActionEvent event) + { + doClick(MenuSelectionManager.defaultManager()); + } + } + + /** + * Creates a new BasicMenuItemUI object. + */ + public BasicMenuItemUI() + { + mouseInputListener = createMouseInputListener(menuItem); + menuDragMouseListener = createMenuDragMouseListener(menuItem); + menuKeyListener = createMenuKeyListener(menuItem); + itemListener = new ItemHandler(); + propertyChangeListener = new PropertyChangeHandler(); + + // Initialize rectangles for layout. + viewRect = new Rectangle(); + textRect = new Rectangle(); + iconRect = new Rectangle(); + arrowIconRect = new Rectangle(); + checkIconRect = new Rectangle(); + accelRect = new Rectangle(); + cachedRect = new Rectangle(); + } + + /** + * Create MenuDragMouseListener to listen for mouse dragged events. + * + * @param c + * menu item to listen to + * @return The MenuDragMouseListener + */ + protected MenuDragMouseListener createMenuDragMouseListener(JComponent c) + { + return new MenuDragMouseHandler(); + } + + /** + * Creates MenuKeyListener to listen to key events occuring when menu item is + * visible on the screen. + * + * @param c + * menu item to listen to + * @return The MenuKeyListener + */ + protected MenuKeyListener createMenuKeyListener(JComponent c) + { + return new MenuKeyHandler(); + } + + /** + * Handles mouse input events occuring for this menu item + * + * @param c + * menu item to listen to + * @return The MouseInputListener + */ + protected MouseInputListener createMouseInputListener(JComponent c) + { + return new MouseInputHandler(); + } + + /** + * Factory method to create a BasicMenuItemUI for the given {@link + * JComponent}, which should be a {@link JMenuItem}. + * + * @param c + * The {@link JComponent} a UI is being created for. + * @return A BasicMenuItemUI for the {@link JComponent}. + */ + public static ComponentUI createUI(JComponent c) + { + return new BasicMenuItemUI(); + } + + /** + * Programatically clicks menu item. + * + * @param msm + * MenuSelectionManager for the menu hierarchy + */ + protected void doClick(MenuSelectionManager msm) + { + menuItem.doClick(0); + msm.clearSelectedPath(); + } + + /** + * Returns maximum size for the specified menu item + * + * @param c + * component for which to get maximum size + * @return Maximum size for the specified menu item. + */ + public Dimension getMaximumSize(JComponent c) + { + return null; + } + + /** + * Returns minimum size for the specified menu item + * + * @param c + * component for which to get minimum size + * @return Minimum size for the specified menu item. + */ + public Dimension getMinimumSize(JComponent c) + { + return null; + } + + /** + * Returns path to this menu item. + * + * @return $MenuElement[]$ Returns array of menu elements that constitute a + * path to this menu item. + */ + public MenuElement[] getPath() + { + ArrayList path = new ArrayList(); + + Component c = menuItem; + while (c instanceof MenuElement) + { + path.add(0, c); + + if (c instanceof JPopupMenu) + c = ((JPopupMenu) c).getInvoker(); + else + c = c.getParent(); + } + + MenuElement[] pathArray = new MenuElement[path.size()]; + path.toArray(pathArray); + return pathArray; + } + + /** + * Returns preferred size for the given menu item. + * + * @param c + * menu item for which to get preferred size + * @param checkIcon + * check icon displayed in the given menu item + * @param arrowIcon + * arrow icon displayed in the given menu item + * @param defaultTextIconGap + * space between icon and text in the given menuItem + * @return $Dimension$ preferred size for the given menu item + */ + protected Dimension getPreferredMenuItemSize(JComponent c, Icon checkIcon, + Icon arrowIcon, + int defaultTextIconGap) + { + JMenuItem m = (JMenuItem) c; + String accelText = getAcceleratorString(m); + + // Layout the menu item. The result gets stored in the rectangle + // fields of this class. + resetRectangles(null); + layoutMenuItem(m, accelText); + + // The union of the text and icon areas is the label area. + cachedRect.setBounds(textRect); + Rectangle pref = SwingUtilities.computeUnion(iconRect.x, iconRect.y, + iconRect.width, + iconRect.height, + cachedRect); + + // Find the widest menu item text and accelerator and store it in + // client properties of the parent, so that we can align the accelerators + // properly. Of course, we only need can do this, if the parent is + // a JComponent and this menu item is not a toplevel menu. + Container parent = m.getParent(); + if (parent != null && parent instanceof JComponent + && !(m instanceof JMenu && ((JMenu) m).isTopLevelMenu())) + { + JComponent p = (JComponent) parent; + + // The widest text so far. + Integer maxTextWidth = (Integer) p.getClientProperty("maxTextWidth"); + int maxTextValue = maxTextWidth == null ? 0 : maxTextWidth.intValue(); + if (pref.width < maxTextValue) + pref.width = maxTextValue; + else + p.putClientProperty("maxTextWidth", new Integer(pref.width)); + + // The widest accelerator so far. + Integer maxAccelWidth = (Integer) p.getClientProperty("maxAccelWidth"); + int maxAccelValue = maxAccelWidth == null ? 0 + : maxAccelWidth.intValue(); + if (accelRect.width > maxAccelValue) + { + maxAccelValue = accelRect.width; + p.putClientProperty("maxAccelWidth", new Integer(accelRect.width)); + } + pref.width += maxAccelValue; + pref.width += defaultTextIconGap; + } + + // Add arrow and check size if appropriate. + if (! (m instanceof JMenu && ((JMenu) m).isTopLevelMenu())) + { + pref.width += checkIconRect.width; + pref.width += defaultTextIconGap; + pref.width += arrowIconRect.width; + pref.width += defaultTextIconGap; + } + + // Add a gap ~2 times as wide as the defaultTextIconGap. + pref.width += 2 * defaultTextIconGap; + + // Respect the insets of the menu item. + Insets i = m.getInsets(); + pref.width += i.left + i.right; + pref.height += i.top + i.bottom; + + // Return a copy, so that nobody messes with our textRect. + return pref.getSize(); + } + + /** + * Returns preferred size of the given component + * + * @param c + * component for which to return preferred size + * @return $Dimension$ preferred size for the given component + */ + public Dimension getPreferredSize(JComponent c) + { + return getPreferredMenuItemSize(c, checkIcon, arrowIcon, + defaultTextIconGap); + } + + /** + * Returns the prefix for entries in the {@link UIDefaults} table. + * + * @return "MenuItem" + */ + protected String getPropertyPrefix() + { + return "MenuItem"; + } + + /** + * This method installs the components for this {@link JMenuItem}. + * + * @param menuItem + * The {@link JMenuItem} to install components for. + */ + protected void installComponents(JMenuItem menuItem) + { + // FIXME: Need to implement + } + + /** + * This method installs the defaults that are defined in the Basic look and + * feel for this {@link JMenuItem}. + */ + protected void installDefaults() + { + String prefix = getPropertyPrefix(); + LookAndFeel.installBorder(menuItem, prefix + ".border"); + LookAndFeel.installColorsAndFont(menuItem, prefix + ".background", + prefix + ".foreground", prefix + ".font"); + menuItem.setMargin(UIManager.getInsets(prefix + ".margin")); + acceleratorFont = UIManager.getFont(prefix + ".acceleratorFont"); + acceleratorForeground = UIManager.getColor(prefix + + ".acceleratorForeground"); + acceleratorSelectionForeground = UIManager.getColor(prefix + + ".acceleratorSelectionForeground"); + selectionBackground = UIManager.getColor(prefix + ".selectionBackground"); + selectionForeground = UIManager.getColor(prefix + ".selectionForeground"); + acceleratorDelimiter = UIManager.getString(prefix + ".acceleratorDelimiter"); + checkIcon = UIManager.getIcon(prefix + ".checkIcon"); + + menuItem.setHorizontalTextPosition(SwingConstants.TRAILING); + menuItem.setHorizontalAlignment(SwingConstants.LEADING); + } + + /** + * This method installs the keyboard actions for this {@link JMenuItem}. + */ + protected void installKeyboardActions() + { + InputMap focusedWindowMap = SwingUtilities.getUIInputMap(menuItem, + JComponent.WHEN_IN_FOCUSED_WINDOW); + if (focusedWindowMap == null) + focusedWindowMap = new ComponentInputMapUIResource(menuItem); + KeyStroke accelerator = menuItem.getAccelerator(); + if (accelerator != null) + focusedWindowMap.put(accelerator, "doClick"); + SwingUtilities.replaceUIInputMap(menuItem, + JComponent.WHEN_IN_FOCUSED_WINDOW, focusedWindowMap); + + ActionMap UIActionMap = SwingUtilities.getUIActionMap(menuItem); + if (UIActionMap == null) + UIActionMap = new ActionMapUIResource(); + UIActionMap.put("doClick", new ClickAction()); + SwingUtilities.replaceUIActionMap(menuItem, UIActionMap); + } + + /** + * This method installs the listeners for the {@link JMenuItem}. + */ + protected void installListeners() + { + menuItem.addMouseListener(mouseInputListener); + menuItem.addMouseMotionListener(mouseInputListener); + menuItem.addMenuDragMouseListener(menuDragMouseListener); + menuItem.addMenuKeyListener(menuKeyListener); + menuItem.addItemListener(itemListener); + menuItem.addPropertyChangeListener(propertyChangeListener); + // Fire synthetic property change event to let the listener update + // the TextLayout cache. + propertyChangeListener.propertyChange(new PropertyChangeEvent(menuItem, + "font", null, + menuItem.getFont())); + } + + /** + * Installs and initializes all fields for this UI delegate. Any properties of + * the UI that need to be initialized and/or set to defaults will be done now. + * It will also install any listeners necessary. + * + * @param c + * The {@link JComponent} that is having this UI installed. + */ + public void installUI(JComponent c) + { + super.installUI(c); + menuItem = (JMenuItem) c; + installDefaults(); + installComponents(menuItem); + installListeners(); + installKeyboardActions(); + } + + /** + * Paints given menu item using specified graphics context + * + * @param g + * The graphics context used to paint this menu item + * @param c + * Menu Item to paint + */ + public void paint(Graphics g, JComponent c) + { + paintMenuItem(g, c, checkIcon, arrowIcon, selectionBackground, + c.getForeground(), defaultTextIconGap); + } + + /** + * Paints background of the menu item + * + * @param g + * The graphics context used to paint this menu item + * @param menuItem + * menu item to paint + * @param bgColor + * Background color to use when painting menu item + */ + protected void paintBackground(Graphics g, JMenuItem menuItem, Color bgColor) + { + // Menu item is considered to be highlighted when it is selected. + // But we don't want to paint the background of JCheckBoxMenuItems + ButtonModel mod = menuItem.getModel(); + Color saved = g.getColor(); + if (mod.isArmed() || ((menuItem instanceof JMenu) && mod.isSelected())) + { + g.setColor(bgColor); + g.fillRect(0, 0, menuItem.getWidth(), menuItem.getHeight()); + } + else if (menuItem.isOpaque()) + { + g.setColor(menuItem.getBackground()); + g.fillRect(0, 0, menuItem.getWidth(), menuItem.getHeight()); + } + g.setColor(saved); + } + + /** + * Paints specified menu item + * + * @param g + * The graphics context used to paint this menu item + * @param c + * menu item to paint + * @param checkIcon + * check icon to use when painting menu item + * @param arrowIcon + * arrow icon to use when painting menu item + * @param background + * Background color of the menu item + * @param foreground + * Foreground color of the menu item + * @param defaultTextIconGap + * space to use between icon and text when painting menu item + */ + protected void paintMenuItem(Graphics g, JComponent c, Icon checkIcon, + Icon arrowIcon, Color background, + Color foreground, int defaultTextIconGap) + { + JMenuItem m = (JMenuItem) c; + + // Fetch fonts. + Font oldFont = g.getFont(); + Font font = c.getFont(); + g.setFont(font); + FontMetrics accelFm = m.getFontMetrics(acceleratorFont); + + // Create accelerator string. + String accelText = getAcceleratorString(m); + + // Layout menu item. The result gets stored in the rectangle fields + // of this class. + resetRectangles(m); + + layoutMenuItem(m, accelText); + + // Paint the background. + paintBackground(g, m, background); + + Color oldColor = g.getColor(); + + // Paint the check icon. + if (checkIcon != null) + { + checkIcon.paintIcon(m, g, checkIconRect.x, checkIconRect.y); + } + + // Paint the icon. + ButtonModel model = m.getModel(); + if (m.getIcon() != null) + { + // Determine icon depending on the menu item + // state (normal/disabled/pressed). + Icon icon; + if (! m.isEnabled()) + { + icon = m.getDisabledIcon(); + } + else if (model.isPressed() && model.isArmed()) + { + icon = m.getPressedIcon(); + if (icon == null) + { + icon = m.getIcon(); + } + } + else + { + icon = m.getIcon(); + } + + if (icon != null) + { + icon.paintIcon(m, g, iconRect.x, iconRect.y); + } + } + + // Paint the text. + String text = m.getText(); + if (text != null) + { + // Handle HTML. + View html = (View) m.getClientProperty(BasicHTML.propertyKey); + if (html != null) + { + html.paint(g, textRect); + } + else + { + paintText(g, m, textRect, text); + } + } + + // Paint accelerator text. + if (! accelText.equals("")) + { + // Align the accelerator text. In getPreferredMenuItemSize() we + // store a client property 'maxAccelWidth' in the parent which holds + // the maximum accelerator width for the children of this parent. + // We use this here to align the accelerators properly. + int accelOffset = 0; + Container parent = m.getParent(); + if (parent != null && parent instanceof JComponent) + { + JComponent p = (JComponent) parent; + Integer maxAccelWidth = + (Integer) p.getClientProperty("maxAccelWidth"); + int maxAccelValue = maxAccelWidth == null ? 0 + : maxAccelWidth.intValue(); + accelOffset = maxAccelValue - accelRect.width; + } + + g.setFont(acceleratorFont); + if (! m.isEnabled()) + { + // Paint accelerator disabled. + g.setColor(disabledForeground); + } + else + { + if (m.isArmed() || (m instanceof JMenu && m.isSelected())) + g.setColor(acceleratorSelectionForeground); + else + g.setColor(acceleratorForeground); + } + g.drawString(accelText, accelRect.x - accelOffset, + accelRect.y + accelFm.getAscent()); + } + + // Paint arrow. + if (arrowIcon != null + && ! (m instanceof JMenu && ((JMenu) m).isTopLevelMenu())) + { + arrowIcon.paintIcon(m, g, arrowIconRect.x, arrowIconRect.y); + } + + g.setFont(oldFont); + g.setColor(oldColor); + + } + + /** + * Paints label for the given menu item + * + * @param g + * The graphics context used to paint this menu item + * @param menuItem + * menu item for which to draw its label + * @param textRect + * rectangle specifiying position of the text relative to the given + * menu item + * @param text + * label of the menu item + */ + protected void paintText(Graphics g, JMenuItem menuItem, Rectangle textRect, + String text) + { + Font f = menuItem.getFont(); + g.setFont(f); + FontMetrics fm = g.getFontMetrics(f); + + if (text != null && !text.equals("")) + { + if (menuItem.isEnabled()) + { + // Menu item is considered to be highlighted when it is selected. + // But not if it's a JCheckBoxMenuItem + ButtonModel mod = menuItem.getModel(); + if ((menuItem.isSelected() && checkIcon == null) + || (mod != null && mod.isArmed()) + && (menuItem.getParent() instanceof MenuElement)) + g.setColor(selectionForeground); + else + g.setColor(menuItem.getForeground()); + } + else + // 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(menuItem, g, text, + mnemonicIndex, + textRect.x, + textRect.y + + fm.getAscent()); + else + BasicGraphicsUtils.drawString(menuItem, g, text, 0, textRect.x, + textRect.y + fm.getAscent()); + } + } + + /** + * This method uninstalls the components for this {@link JMenuItem}. + * + * @param menuItem + * The {@link JMenuItem} to uninstall components for. + */ + protected void uninstallComponents(JMenuItem menuItem) + { + // FIXME: need to implement + } + + /** + * This method uninstalls the defaults and sets any objects created during + * install to null + */ + protected void uninstallDefaults() + { + menuItem.setForeground(null); + menuItem.setBackground(null); + menuItem.setBorder(null); + menuItem.setMargin(null); + menuItem.setBackground(null); + menuItem.setBorder(null); + menuItem.setFont(null); + menuItem.setForeground(null); + menuItem.setMargin(null); + acceleratorFont = null; + acceleratorForeground = null; + acceleratorSelectionForeground = null; + arrowIcon = null; + selectionBackground = null; + selectionForeground = null; + acceleratorDelimiter = null; + } + + /** + * Uninstalls any keyboard actions. + */ + protected void uninstallKeyboardActions() + { + SwingUtilities.replaceUIInputMap(menuItem, + JComponent.WHEN_IN_FOCUSED_WINDOW, null); + } + + /** + * Unregisters all the listeners that this UI delegate was using. + */ + protected void uninstallListeners() + { + menuItem.removeMouseListener(mouseInputListener); + menuItem.removeMenuDragMouseListener(menuDragMouseListener); + menuItem.removeMenuKeyListener(menuKeyListener); + menuItem.removeItemListener(itemListener); + menuItem.removePropertyChangeListener(propertyChangeListener); + } + + /** + * Performs the opposite of installUI. Any properties or resources that need + * to be cleaned up will be done now. It will also uninstall any listeners it + * has. In addition, any properties of this UI will be nulled. + * + * @param c + * The {@link JComponent} that is having this UI uninstalled. + */ + public void uninstallUI(JComponent c) + { + uninstallListeners(); + uninstallDefaults(); + uninstallComponents(menuItem); + c.putClientProperty(BasicGraphicsUtils.CACHED_TEXT_LAYOUT, null); + menuItem = null; + } + + /** + * This method calls paint. + * + * @param g + * The graphics context used to paint this menu item + * @param c + * The menu item to paint + */ + public void update(Graphics g, JComponent c) + { + paint(g, c); + } + + /** + * This class handles mouse events occuring inside the menu item. Most of the + * events are forwarded for processing to MenuSelectionManager of the current + * menu hierarchy. + */ + protected class MouseInputHandler implements MouseInputListener + { + /** + * Creates a new MouseInputHandler object. + */ + protected MouseInputHandler() + { + // Nothing to do here. + } + + /** + * This method is called when mouse is clicked on the menu item. It forwards + * this event to MenuSelectionManager. + * + * @param e + * A {@link MouseEvent}. + */ + public void mouseClicked(MouseEvent e) + { + MenuSelectionManager manager = MenuSelectionManager.defaultManager(); + manager.processMouseEvent(e); + } + + /** + * This method is called when mouse is dragged inside the menu item. It + * forwards this event to MenuSelectionManager. + * + * @param e + * A {@link MouseEvent}. + */ + public void mouseDragged(MouseEvent e) + { + MenuSelectionManager manager = MenuSelectionManager.defaultManager(); + manager.processMouseEvent(e); + } + + /** + * This method is called when mouse enters menu item. When this happens menu + * item is considered to be selected and selection path in + * MenuSelectionManager is set. This event is also forwarded to + * MenuSelection Manager for further processing. + * + * @param e + * A {@link MouseEvent}. + */ + public void mouseEntered(MouseEvent e) + { + Component source = (Component) e.getSource(); + if (source.getParent() instanceof MenuElement) + { + MenuSelectionManager manager = MenuSelectionManager.defaultManager(); + manager.setSelectedPath(getPath()); + manager.processMouseEvent(e); + } + } + + /** + * This method is called when mouse exits menu item. The event is forwarded + * to MenuSelectionManager for processing. + * + * @param e + * A {@link MouseEvent}. + */ + public void mouseExited(MouseEvent e) + { + MenuSelectionManager manager = MenuSelectionManager.defaultManager(); + manager.processMouseEvent(e); + } + + /** + * This method is called when mouse is inside the menu item. This event is + * forwarder to MenuSelectionManager for further processing. + * + * @param e + * A {@link MouseEvent}. + */ + public void mouseMoved(MouseEvent e) + { + MenuSelectionManager manager = MenuSelectionManager.defaultManager(); + manager.processMouseEvent(e); + } + + /** + * This method is called when mouse is pressed. This event is forwarded to + * MenuSelectionManager for further processing. + * + * @param e + * A {@link MouseEvent}. + */ + public void mousePressed(MouseEvent e) + { + MenuSelectionManager manager = MenuSelectionManager.defaultManager(); + manager.processMouseEvent(e); + } + + /** + * This method is called when mouse is released. If the mouse is released + * inside this menuItem, then this menu item is considered to be chosen and + * the menu hierarchy should be closed. + * + * @param e + * A {@link MouseEvent}. + */ + public void mouseReleased(MouseEvent e) + { + MenuSelectionManager manager = MenuSelectionManager.defaultManager(); + int x = e.getX(); + int y = e.getY(); + if (x > 0 && x < menuItem.getWidth() && y > 0 + && y < menuItem.getHeight()) + { + doClick(manager); + } + else + manager.processMouseEvent(e); + } + } + + /** + * This class handles mouse dragged events. + */ + private class MenuDragMouseHandler implements MenuDragMouseListener + { + /** + * Tbis method is invoked when mouse is dragged over the menu item. + * + * @param e + * The MenuDragMouseEvent + */ + public void menuDragMouseDragged(MenuDragMouseEvent e) + { + MenuSelectionManager manager = e.getMenuSelectionManager(); + manager.setSelectedPath(e.getPath()); + } + + /** + * Tbis method is invoked when mouse enters the menu item while it is being + * dragged. + * + * @param e + * The MenuDragMouseEvent + */ + public void menuDragMouseEntered(MenuDragMouseEvent e) + { + MenuSelectionManager manager = e.getMenuSelectionManager(); + manager.setSelectedPath(e.getPath()); + } + + /** + * Tbis method is invoked when mouse exits the menu item while it is being + * dragged + * + * @param e the MenuDragMouseEvent + */ + public void menuDragMouseExited(MenuDragMouseEvent e) + { + // Nothing to do here yet. + } + + /** + * Tbis method is invoked when mouse was dragged and released inside the + * menu item. + * + * @param e + * The MenuDragMouseEvent + */ + public void menuDragMouseReleased(MenuDragMouseEvent e) + { + MenuSelectionManager manager = e.getMenuSelectionManager(); + int x = e.getX(); + int y = e.getY(); + if (x >= 0 && x < menuItem.getWidth() && y >= 0 + && y < menuItem.getHeight()) + doClick(manager); + else + manager.clearSelectedPath(); + } + } + + /** + * This class handles key events occuring when menu item is visible on the + * screen. + */ + private class MenuKeyHandler implements MenuKeyListener + { + /** + * This method is invoked when key has been pressed + * + * @param e + * A {@link MenuKeyEvent}. + */ + public void menuKeyPressed(MenuKeyEvent e) + { + // TODO: What should be done here, if anything? + } + + /** + * This method is invoked when key has been pressed + * + * @param e + * A {@link MenuKeyEvent}. + */ + public void menuKeyReleased(MenuKeyEvent e) + { + // TODO: What should be done here, if anything? + } + + /** + * This method is invoked when key has been typed It handles the mnemonic + * key for the menu item. + * + * @param e + * A {@link MenuKeyEvent}. + */ + public void menuKeyTyped(MenuKeyEvent e) + { + // TODO: What should be done here, if anything? + } + } + + /** + * Helper class that listens for item changes to the properties of the {@link + * JMenuItem}. + */ + private class ItemHandler implements ItemListener + { + /** + * This method is called when one of the menu item changes. + * + * @param evt A {@link ItemEvent}. + */ + public void itemStateChanged(ItemEvent evt) + { + boolean state = false; + if (menuItem instanceof JCheckBoxMenuItem) + { + if (evt.getStateChange() == ItemEvent.SELECTED) + state = true; + ((JCheckBoxMenuItem) menuItem).setState(state); + } + menuItem.revalidate(); + menuItem.repaint(); + } + } + + /** + * A helper method to create the accelerator string from the menu item's + * accelerator property. The returned string is empty if there is + * no accelerator defined. + * + * @param m the menu item + * + * @return the accelerator string, not null + */ + private String getAcceleratorString(JMenuItem m) + { + // Create accelerator string. + KeyStroke accel = m.getAccelerator(); + String accelText = ""; + if (accel != null) + { + int mods = accel.getModifiers(); + if (mods > 0) + { + accelText = KeyEvent.getKeyModifiersText(mods); + accelText += acceleratorDelimiter; + } + int keycode = accel.getKeyCode(); + if (keycode != 0) + accelText += KeyEvent.getKeyText(keycode); + else + accelText += accel.getKeyChar(); + } + return accelText; + } + + /** + * Resets the cached layout rectangles. If <code>i</code> is not null, then + * the view rectangle is set to the inner area of the component, otherwise + * it is set to (0, 0, Short.MAX_VALUE, Short.MAX_VALUE), this is needed + * for layouting. + * + * @param i the component for which to initialize the rectangles + */ + private void resetRectangles(JMenuItem i) + { + // Reset rectangles. + iconRect.setBounds(0, 0, 0, 0); + textRect.setBounds(0, 0, 0, 0); + accelRect.setBounds(0, 0, 0, 0); + checkIconRect.setBounds(0, 0, 0, 0); + arrowIconRect.setBounds(0, 0, 0, 0); + if (i == null) + viewRect.setBounds(0, 0, Short.MAX_VALUE, Short.MAX_VALUE); + else + { + Insets insets = i.getInsets(); + viewRect.setBounds(insets.left, insets.top, + i.getWidth() - insets.left - insets.right, + i.getHeight() - insets.top - insets.bottom); + } + } + + /** + * A helper method that lays out the menu item. The layout is stored + * in the fields of this class. + * + * @param m the menu item to layout + * @param accelText the accelerator text + */ + private void layoutMenuItem(JMenuItem m, String accelText) + { + // Fetch the fonts. + Font font = m.getFont(); + FontMetrics fm = m.getFontMetrics(font); + FontMetrics accelFm = m.getFontMetrics(acceleratorFont); + + String text = m.getText(); + SwingUtilities.layoutCompoundLabel(m, fm, text, m.getIcon(), + m.getVerticalAlignment(), + m.getHorizontalAlignment(), + m.getVerticalTextPosition(), + m.getHorizontalTextPosition(), + viewRect, iconRect, textRect, + defaultTextIconGap); + + // Initialize accelerator width and height. + if (! accelText.equals("")) + { + accelRect.width = accelFm.stringWidth(accelText); + accelRect.height = accelFm.getHeight(); + } + + // Initialize check and arrow icon width and height. + if (! (m instanceof JMenu && ((JMenu) m).isTopLevelMenu())) + { + if (checkIcon != null) + { + checkIconRect.width = checkIcon.getIconWidth(); + checkIconRect.height = checkIcon.getIconHeight(); + } + if (arrowIcon != null) + { + arrowIconRect.width = arrowIcon.getIconWidth(); + arrowIconRect.height = arrowIcon.getIconHeight(); + } + } + + // The union of the icon and text of the menu item is the 'label area'. + cachedRect.setBounds(textRect); + Rectangle labelRect = SwingUtilities.computeUnion(iconRect.x, + iconRect.y, + iconRect.width, + iconRect.height, + cachedRect); + textRect.x += defaultTextIconGap; + iconRect.x += defaultTextIconGap; + + // Layout accelerator rect. + accelRect.x = viewRect.x + viewRect.width - arrowIconRect.width + - defaultTextIconGap - accelRect.width; + // Layout check and arrow icons only when not in toplevel menu. + if (! (m instanceof JMenu && ((JMenu) m).isTopLevelMenu())) + { + checkIconRect.x = viewRect.x + defaultTextIconGap; + textRect.x += defaultTextIconGap + checkIconRect.width; + iconRect.x += defaultTextIconGap + checkIconRect.width; + arrowIconRect.x = viewRect.x + viewRect.width - defaultTextIconGap + - arrowIconRect.width; + } + + // Align the accelerator text and all the icons vertically centered to + // the menu text. + accelRect.y = labelRect.y + (labelRect.height / 2) + - (accelRect.height / 2); + if (! (m instanceof JMenu && ((JMenu) m).isTopLevelMenu())) + { + arrowIconRect.y = labelRect.y + (labelRect.height / 2) + - (arrowIconRect.height / 2); + checkIconRect.y = labelRect.y + (labelRect.height / 2) + - (checkIconRect.height / 2); + } + } +} diff --git a/libjava/classpath/javax/swing/plaf/basic/BasicMenuUI.java b/libjava/classpath/javax/swing/plaf/basic/BasicMenuUI.java new file mode 100644 index 000000000..4897ee4a0 --- /dev/null +++ b/libjava/classpath/javax/swing/plaf/basic/BasicMenuUI.java @@ -0,0 +1,678 @@ +/* BasicMenuUI.java + Copyright (C) 2002, 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.NotImplementedException; + +import java.awt.Component; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.Point; +import java.awt.event.ActionEvent; +import java.awt.event.MouseEvent; +import java.beans.PropertyChangeListener; + +import javax.swing.AbstractAction; +import javax.swing.JComponent; +import javax.swing.JMenu; +import javax.swing.JMenuBar; +import javax.swing.JPopupMenu; +import javax.swing.LookAndFeel; +import javax.swing.MenuElement; +import javax.swing.MenuSelectionManager; +import javax.swing.Timer; +import javax.swing.UIDefaults; +import javax.swing.UIManager; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import javax.swing.event.MenuDragMouseEvent; +import javax.swing.event.MenuDragMouseListener; +import javax.swing.event.MenuEvent; +import javax.swing.event.MenuKeyEvent; +import javax.swing.event.MenuKeyListener; +import javax.swing.event.MenuListener; +import javax.swing.event.MouseInputListener; +import javax.swing.plaf.ComponentUI; + +/** + * UI Delegate for JMenu + */ +public class BasicMenuUI extends BasicMenuItemUI +{ + /** + * Selects a menu. This is used to delay menu selection. + */ + class SelectMenuAction + extends AbstractAction + { + /** + * Performs the action. + */ + public void actionPerformed(ActionEvent event) + { + JMenu menu = (JMenu) menuItem; + MenuSelectionManager defaultManager = + MenuSelectionManager.defaultManager(); + MenuElement path[] = defaultManager.getSelectedPath(); + if(path.length > 0 && path[path.length - 1] == menu) + { + MenuElement newPath[] = new MenuElement[path.length + 1]; + System.arraycopy(path, 0, newPath, 0, path.length); + newPath[path.length] = menu.getPopupMenu(); + defaultManager.setSelectedPath(newPath); + } + } + + } + + protected ChangeListener changeListener; + + /* MenuListener listens to MenuEvents fired by JMenu */ + protected MenuListener menuListener; + + /* PropertyChangeListner that listens to propertyChangeEvents occuring in JMenu*/ + protected PropertyChangeListener propertyChangeListener; + + /** + * Creates a new BasicMenuUI object. + */ + public BasicMenuUI() + { + mouseInputListener = createMouseInputListener((JMenu) menuItem); + menuListener = createMenuListener((JMenu) menuItem); + propertyChangeListener = createPropertyChangeListener((JMenu) menuItem); + } + + /** + * This method creates a new ChangeListener. + * + * @return A new ChangeListener. + */ + protected ChangeListener createChangeListener(JComponent c) + { + return new ChangeHandler((JMenu) c, this); + } + + /** + * This method creates new MenuDragMouseListener to listen to mouse dragged events + * occuring in the Menu + * + * @param c the menu to listen to + * + * @return The MenuDrageMouseListener + */ + protected MenuDragMouseListener createMenuDragMouseListener(JComponent c) + { + return new MenuDragMouseHandler(); + } + + /** + * This method creates new MenuDragKeyListener to listen to key events + * + * @param c the menu to listen to + * + * @return The MenuKeyListener + */ + protected MenuKeyListener createMenuKeyListener(JComponent c) + { + return new MenuKeyHandler(); + } + + /** + * This method creates new MenuListener to listen to menu events + * occuring in the Menu + * + * @param c the menu to listen to + * + * @return The MenuListener + */ + protected MenuListener createMenuListener(JComponent c) + { + return new MenuHandler(); + } + + /** + * This method creates new MouseInputListener to listen to mouse input events + * occuring in the Menu + * + * @param c the menu to listen to + * + * @return The MouseInputListener + */ + protected MouseInputListener createMouseInputListener(JComponent c) + { + return new MouseInputHandler(); + } + + /** + * This method creates newPropertyChangeListener to listen to property changes + * occuring in the Menu + * + * @param c the menu to listen to + * + * @return The PropertyChangeListener + */ + protected PropertyChangeListener createPropertyChangeListener(JComponent c) + { + return new PropertyChangeHandler(); + } + + /** + * This method creates a new BasicMenuUI. + * + * @param c The JComponent to create a UI for. + * + * @return A new BasicMenuUI. + */ + public static ComponentUI createUI(JComponent c) + { + return new BasicMenuUI(); + } + + /** + * Get the component's maximum size. + * + * @param c The JComponent for which to get maximum size + * + * @return The maximum size of the component + */ + public Dimension getMaximumSize(JComponent c) + { + return c.getPreferredSize(); + } + + /** + * Returns the prefix for entries in the {@link UIDefaults} table. + * + * @return "Menu" + */ + protected String getPropertyPrefix() + { + return "Menu"; + } + + /** + * Initializes any default properties that this UI has from the defaults for + * the Basic look and feel. + */ + protected void installDefaults() + { + + LookAndFeel.installBorder(menuItem, "Menu.border"); + LookAndFeel.installColorsAndFont(menuItem, "Menu.background", + "Menu.foreground", "Menu.font"); + menuItem.setMargin(UIManager.getInsets("Menu.margin")); + acceleratorFont = UIManager.getFont("Menu.acceleratorFont"); + acceleratorForeground = UIManager.getColor("Menu.acceleratorForeground"); + acceleratorSelectionForeground = UIManager.getColor("Menu.acceleratorSelectionForeground"); + selectionBackground = UIManager.getColor("Menu.selectionBackground"); + selectionForeground = UIManager.getColor("Menu.selectionForeground"); + arrowIcon = UIManager.getIcon("Menu.arrowIcon"); + oldBorderPainted = UIManager.getBoolean("Menu.borderPainted"); + ((JMenu) menuItem).setDelay(200); + } + + /** + * Installs any keyboard actions. The list of keys that need to be bound are + * listed in Basic look and feel's defaults. + * + */ + protected void installKeyboardActions() + { + super.installKeyboardActions(); + } + + /** + * Creates and registers all the listeners for this UI delegate. + */ + protected void installListeners() + { + super.installListeners(); + ((JMenu) menuItem).addMenuListener(menuListener); + } + + protected void setupPostTimer(JMenu menu) + { + Timer timer = new Timer(menu.getDelay(), new SelectMenuAction()); + timer.setRepeats(false); + timer.start(); + } + + /** + * This method uninstalls the defaults and sets any objects created during + * install to null + */ + protected void uninstallDefaults() + { + menuItem.setBackground(null); + menuItem.setBorder(null); + menuItem.setFont(null); + menuItem.setForeground(null); + menuItem.setMargin(null); + acceleratorFont = null; + acceleratorForeground = null; + acceleratorSelectionForeground = null; + selectionBackground = null; + selectionForeground = null; + arrowIcon = null; + } + + /** + * Uninstalls any keyboard actions. The list of keys used are listed in + * Basic look and feel's defaults. + */ + protected void uninstallKeyboardActions() + { + super.installKeyboardActions(); + } + + /** + * 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() + { + super.uninstallListeners(); + ((JMenu) menuItem).removeMenuListener(menuListener); + } + + /** + * This class is used by menus to handle mouse events occuring in the + * menu. + */ + protected class MouseInputHandler implements MouseInputListener + { + public void mouseClicked(MouseEvent e) + { + // Nothing to do here. + } + + public void mouseDragged(MouseEvent e) + { + MenuSelectionManager manager = MenuSelectionManager.defaultManager(); + manager.processMouseEvent(e); + } + + private boolean popupVisible() + { + JMenuBar mb = (JMenuBar) ((JMenu) menuItem).getParent(); + // check if mb.isSelected because if no menus are selected + // we don't have to look through the list for popup menus + if (!mb.isSelected()) + return false; + for (int i = 0; i < mb.getMenuCount(); i++) + { + JMenu m = mb.getMenu(i); + if (m != null && m.isPopupMenuVisible()) + return true; + } + return false; + } + + public void mouseEntered(MouseEvent e) + { + JMenu menu = (JMenu) menuItem; + if (menu.isEnabled()) + { + MenuSelectionManager manager = + MenuSelectionManager.defaultManager(); + MenuElement[] selectedPath = manager.getSelectedPath(); + if (! menu.isTopLevelMenu()) + { + // Open the menu immediately or delayed, depending on the + // delay value. + if(! (selectedPath.length > 0 + && selectedPath[selectedPath.length - 1] == menu.getPopupMenu())) + { + if(menu.getDelay() == 0) + { + MenuElement[] path = getPath(); + MenuElement[] newPath = new MenuElement[path.length + 1]; + System.arraycopy(path, 0, newPath, 0, path.length); + newPath[path.length] = menu.getPopupMenu(); + manager.setSelectedPath(newPath); + } + else + { + manager.setSelectedPath(getPath()); + setupPostTimer(menu); + } + } + } + else + { + if(selectedPath.length > 0 + && selectedPath[0] == menu.getParent()) + { + MenuElement[] newPath = new MenuElement[3]; + newPath[0] = (MenuElement) menu.getParent(); + newPath[1] = menu; + newPath[2] = menu.getPopupMenu(); + manager.setSelectedPath(newPath); + } + } + } + } + + public void mouseExited(MouseEvent e) + { + MenuSelectionManager manager = MenuSelectionManager.defaultManager(); + manager.processMouseEvent(e); + } + + public void mouseMoved(MouseEvent e) + { + // Nothing to do here. + } + + public void mousePressed(MouseEvent e) + { + MenuSelectionManager manager = MenuSelectionManager.defaultManager(); + JMenu menu = (JMenu) menuItem; + if (menu.isEnabled()) + { + // Open up the menu immediately if it's a toplevel menu. + // But not yet the popup, which might be opened delayed, see below. + if (menu.isTopLevelMenu()) + { + if (menu.isSelected()) + manager.clearSelectedPath(); + else + { + Container cnt = menu.getParent(); + if (cnt != null && cnt instanceof JMenuBar) + { + MenuElement[] me = new MenuElement[2]; + me[0] = (MenuElement) cnt; + me[1] = menu; + manager.setSelectedPath(me); + } + } + } + + // Open the menu's popup. Either do that immediately if delay == 0, + // or delayed when delay > 0. + MenuElement[] selectedPath = manager.getSelectedPath(); + if (selectedPath.length > 0 + && selectedPath[selectedPath.length - 1] != menu.getPopupMenu()) + { + if(menu.isTopLevelMenu() || menu.getDelay() == 0) + { + MenuElement[] newPath = + new MenuElement[selectedPath.length + 1]; + System.arraycopy(selectedPath, 0, newPath, 0, + selectedPath.length); + newPath[selectedPath.length] = menu.getPopupMenu(); + manager.setSelectedPath(newPath); + } + else + { + setupPostTimer(menu); + } + } + + } + } + + public void mouseReleased(MouseEvent e) + { + MenuSelectionManager manager = MenuSelectionManager.defaultManager(); + manager.processMouseEvent(e); + } + } + + /** + * This class handles MenuEvents fired by the JMenu + */ + private class MenuHandler implements MenuListener + { + /** + * This method is called when menu is cancelled. The menu is cancelled + * when its popup menu is closed without selection. It clears selected index + * in the selectionModel of the menu parent. + * + * @param e The MenuEvent. + */ + public void menuCanceled(MenuEvent e) + { + menuDeselected(e); + } + + /** + * This method is called when menu is deselected. It clears selected index + * in the selectionModel of the menu parent. + * + * @param e The MenuEvent. + */ + public void menuDeselected(MenuEvent e) + { + JMenu menu = (JMenu) menuItem; + if (menu.getParent() != null) + { + if (menu.isTopLevelMenu()) + ((JMenuBar) menu.getParent()).getSelectionModel().clearSelection(); + else + ((JPopupMenu) menu.getParent()).getSelectionModel().clearSelection(); + } + } + + /** + * This method is called when menu is selected. It sets selected index + * in the selectionModel of the menu parent. + * + * @param e The MenuEvent. + */ + public void menuSelected(MenuEvent e) + { + JMenu menu = (JMenu) menuItem; + if (menu.isTopLevelMenu()) + ((JMenuBar) menu.getParent()).setSelected(menu); + else + ((JPopupMenu) menu.getParent()).setSelected(menu); + } + } + + /** + * Obsolete as of JDK1.4. + */ + public class ChangeHandler implements ChangeListener + { + /** + * Not used. + */ + public boolean isSelected; + + /** + * Not used. + */ + public JMenu menu; + + /** + * Not used. + */ + public BasicMenuUI ui; + + /** + * Not used. + */ + public Component wasFocused; + + /** + * Not used. + */ + public ChangeHandler(JMenu m, BasicMenuUI ui) + { + menu = m; + this.ui = ui; + } + + /** + * Not used. + */ + public void stateChanged(ChangeEvent e) + { + // Not used. + } + } + + /** + * This class handles mouse dragged events occuring in the menu. + */ + private class MenuDragMouseHandler implements MenuDragMouseListener + { + /** + * This method is invoked when mouse is dragged over the menu item. + * + * @param e The MenuDragMouseEvent + */ + public void menuDragMouseDragged(MenuDragMouseEvent e) + { + if (menuItem.isEnabled()) + { + MenuSelectionManager manager = e.getMenuSelectionManager(); + MenuElement path[] = e.getPath(); + + Point p = e.getPoint(); + if(p.x >= 0 && p.x < menuItem.getWidth() + && p.y >= 0 && p.y < menuItem.getHeight()) + { + JMenu menu = (JMenu) menuItem; + MenuElement[] selectedPath = manager.getSelectedPath(); + if(! (selectedPath.length > 0 + && selectedPath[selectedPath.length-1] + == menu.getPopupMenu())) + { + if(menu.isTopLevelMenu() || menu.getDelay() == 0 + || e.getID() == MouseEvent.MOUSE_DRAGGED) + { + MenuElement[] newPath = new MenuElement[path.length + 1]; + System.arraycopy(path, 0, newPath, 0, path.length); + newPath[path.length] = menu.getPopupMenu(); + manager.setSelectedPath(newPath); + } + else + { + manager.setSelectedPath(path); + setupPostTimer(menu); + } + } + } + else if (e.getID() == MouseEvent.MOUSE_RELEASED) + { + Component comp = manager.componentForPoint(e.getComponent(), + e.getPoint()); + if (comp == null) + manager.clearSelectedPath(); + } + } + } + + /** + * This method is invoked when mouse enters the menu item while it is + * being dragged. + * + * @param e The MenuDragMouseEvent + */ + public void menuDragMouseEntered(MenuDragMouseEvent e) + { + // Nothing to do here. + } + + /** + * This method is invoked when mouse exits the menu item while + * it is being dragged + * + * @param e The MenuDragMouseEvent + */ + public void menuDragMouseExited(MenuDragMouseEvent e) + { + // Nothing to do here. + } + + /** + * This method is invoked when mouse was dragged and released + * inside the menu item. + * + * @param e The MenuDragMouseEvent + */ + public void menuDragMouseReleased(MenuDragMouseEvent e) + { + // Nothing to do here. + } + } + + /** + * This class handles key events occuring when menu item is visible on the + * screen. + */ + private class MenuKeyHandler implements MenuKeyListener + { + /** + * This method is invoked when key has been pressed + * + * @param e A {@link MenuKeyEvent}. + */ + public void menuKeyPressed(MenuKeyEvent e) + { + // Nothing to do here. + } + + /** + * This method is invoked when key has been pressed + * + * @param e A {@link MenuKeyEvent}. + */ + public void menuKeyReleased(MenuKeyEvent e) + { + // Nothing to do here. + } + + /** + * This method is invoked when key has been typed + * It handles the mnemonic key for the menu item. + * + * @param e A {@link MenuKeyEvent}. + */ + public void menuKeyTyped(MenuKeyEvent e) + throws NotImplementedException + { + // TODO: What should be done here, if anything? + } + } +} diff --git a/libjava/classpath/javax/swing/plaf/basic/BasicOptionPaneUI.java b/libjava/classpath/javax/swing/plaf/basic/BasicOptionPaneUI.java new file mode 100644 index 000000000..a03d224d2 --- /dev/null +++ b/libjava/classpath/javax/swing/plaf/basic/BasicOptionPaneUI.java @@ -0,0 +1,1404 @@ +/* BasicOptionPaneUI.java -- + Copyright (C) 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 java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Component; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.Graphics; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.LayoutManager; +import java.awt.Polygon; +import java.awt.Window; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.beans.PropertyVetoException; + +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.ActionMap; +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.Icon; +import javax.swing.InputMap; +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JComponent; +import javax.swing.JDialog; +import javax.swing.JInternalFrame; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JTextField; +import javax.swing.LookAndFeel; +import javax.swing.SwingUtilities; +import javax.swing.UIManager; +import javax.swing.border.Border; +import javax.swing.plaf.ActionMapUIResource; +import javax.swing.plaf.ComponentUI; +import javax.swing.plaf.OptionPaneUI; + +/** + * This class is the UI delegate for JOptionPane in the Basic Look and Feel. + */ +public class BasicOptionPaneUI extends OptionPaneUI +{ + /** + * Implements the "close" keyboard action. + */ + static class OptionPaneCloseAction + extends AbstractAction + { + + public void actionPerformed(ActionEvent event) + { + JOptionPane op = (JOptionPane) event.getSource(); + op.setValue(new Integer(JOptionPane.CLOSED_OPTION)); + } + + } + + /** + * This is a helper class that listens to the buttons located at the bottom + * of the JOptionPane. + * + * @specnote Apparently this class was intended to be protected, + * but was made public by a compiler bug and is now + * public for compatibility. + */ + public class ButtonActionListener implements ActionListener + { + /** The index of the option this button represents. */ + protected int buttonIndex; + + /** + * Creates a new ButtonActionListener object with the given buttonIndex. + * + * @param buttonIndex The index of the option this button represents. + */ + public ButtonActionListener(int buttonIndex) + { + this.buttonIndex = buttonIndex; + } + + /** + * This method is called when one of the option buttons are pressed. + * + * @param e The ActionEvent. + */ + public void actionPerformed(ActionEvent e) + { + Object value = new Integer(JOptionPane.CLOSED_OPTION); + Object[] options = optionPane.getOptions(); + if (options != null) + value = new Integer(buttonIndex); + else + { + String text = ((JButton) e.getSource()).getText(); + if (text.equals(OK_STRING)) + value = new Integer(JOptionPane.OK_OPTION); + if (text.equals(CANCEL_STRING)) + value = new Integer(JOptionPane.CANCEL_OPTION); + if (text.equals(YES_STRING)) + value = new Integer(JOptionPane.YES_OPTION); + if (text.equals(NO_STRING)) + value = new Integer(JOptionPane.NO_OPTION); + } + optionPane.setValue(value); + resetInputValue(); + + Window owner = SwingUtilities.windowForComponent(optionPane); + + if (owner instanceof JDialog) + ((JDialog) owner).dispose(); + + //else we probably have some kind of internal frame. + JInternalFrame inf = (JInternalFrame) SwingUtilities.getAncestorOfClass( + JInternalFrame.class, optionPane); + if (inf != null) + { + try + { + inf.setClosed(true); + } + catch (PropertyVetoException pve) + { + // We do nothing if attempt has been vetoed. + } + } + } + } + + /** + * This helper layout manager is responsible for the layout of the button + * area. The button area is the panel that holds the buttons which + * represent the options. + * + * @specnote Apparently this class was intended to be protected, + * but was made public by a compiler bug and is now + * public for compatibility. + */ + public static class ButtonAreaLayout implements LayoutManager + { + /** Whether this layout will center the buttons. */ + protected boolean centersChildren = true; + + /** The space between the buttons. */ + protected int padding; + + /** Whether the buttons will share the same widths. */ + protected boolean syncAllWidths; + + /** The width of the widest button. */ + private transient int widthOfWidestButton; + + /** The height of the tallest button. */ + private transient int tallestButton; + + /** + * Creates a new ButtonAreaLayout object with the given sync widths + * property and padding. + * + * @param syncAllWidths Whether the buttons will share the same widths. + * @param padding The padding between the buttons. + */ + public ButtonAreaLayout(boolean syncAllWidths, int padding) + { + this.syncAllWidths = syncAllWidths; + this.padding = padding; + } + + /** + * This method is called when a component is added to the container. + * + * @param string The constraints string. + * @param comp The component added. + */ + public void addLayoutComponent(String string, Component comp) + { + // Do nothing. + } + + /** + * This method returns whether the children will be centered. + * + * @return Whether the children will be centered. + */ + public boolean getCentersChildren() + { + return centersChildren; + } + + /** + * This method returns the amount of space between components. + * + * @return The amount of space between components. + */ + public int getPadding() + { + return padding; + } + + /** + * This method returns whether all components will share widths (set to + * largest width). + * + * @return Whether all components will share widths. + */ + public boolean getSyncAllWidths() + { + return syncAllWidths; + } + + /** + * This method lays out the given container. + * + * @param container The container to lay out. + */ + public void layoutContainer(Container container) + { + Component[] buttonList = container.getComponents(); + int x = container.getInsets().left; + if (getCentersChildren()) + x += (int) ((double) (container.getSize().width) / 2 + - (double) (buttonRowLength(container)) / 2); + for (int i = 0; i < buttonList.length; i++) + { + Dimension dims = buttonList[i].getPreferredSize(); + if (syncAllWidths) + { + buttonList[i].setBounds(x, 0, widthOfWidestButton, dims.height); + x += widthOfWidestButton + getPadding(); + } + else + { + buttonList[i].setBounds(x, 0, dims.width, dims.height); + x += dims.width + getPadding(); + } + } + } + + /** + * This method returns the width of the given container taking into + * consideration the padding and syncAllWidths. + * + * @param c The container to calculate width for. + * + * @return The width of the given container. + */ + private int buttonRowLength(Container c) + { + Component[] buttonList = c.getComponents(); + + int buttonLength = 0; + int widest = 0; + int tallest = 0; + + for (int i = 0; i < buttonList.length; i++) + { + Dimension dims = buttonList[i].getPreferredSize(); + buttonLength += dims.width + getPadding(); + widest = Math.max(widest, dims.width); + tallest = Math.max(tallest, dims.height); + } + + widthOfWidestButton = widest; + tallestButton = tallest; + + int width; + if (getSyncAllWidths()) + width = widest * buttonList.length + + getPadding() * (buttonList.length - 1); + else + width = buttonLength; + + Insets insets = c.getInsets(); + width += insets.left + insets.right; + + return width; + } + + /** + * This method returns the minimum layout size for the given container. + * + * @param c The container to measure. + * + * @return The minimum layout size. + */ + public Dimension minimumLayoutSize(Container c) + { + return preferredLayoutSize(c); + } + + /** + * This method returns the preferred size of the given container. + * + * @param c The container to measure. + * + * @return The preferred size. + */ + public Dimension preferredLayoutSize(Container c) + { + int w = buttonRowLength(c); + + return new Dimension(w, tallestButton); + } + + /** + * This method removes the given component from the layout manager's + * knowledge. + * + * @param c The component to remove. + */ + public void removeLayoutComponent(Component c) + { + // Do nothing. + } + + /** + * This method sets whether the children will be centered. + * + * @param newValue Whether the children will be centered. + */ + public void setCentersChildren(boolean newValue) + { + centersChildren = newValue; + } + + /** + * This method sets the amount of space between each component. + * + * @param newPadding The padding between components. + */ + public void setPadding(int newPadding) + { + padding = newPadding; + } + + /** + * This method sets whether the widths will be synced. + * + * @param newValue Whether the widths will be synced. + */ + public void setSyncAllWidths(boolean newValue) + { + syncAllWidths = newValue; + } + } + + /** + * This helper class handles property change events from the JOptionPane. + * + * @specnote Apparently this class was intended to be protected, + * but was made public by a compiler bug and is now + * public for compatibility. + */ + public class PropertyChangeHandler implements PropertyChangeListener + { + /** + * This method is called when one of the properties of the JOptionPane + * changes. + * + * @param e The PropertyChangeEvent. + */ + public void propertyChange(PropertyChangeEvent e) + { + String property = e.getPropertyName(); + if (property.equals(JOptionPane.ICON_PROPERTY) + || property.equals(JOptionPane.INITIAL_SELECTION_VALUE_PROPERTY) + || property.equals(JOptionPane.INITIAL_VALUE_PROPERTY) + || property.equals(JOptionPane.MESSAGE_PROPERTY) + || property.equals(JOptionPane.MESSAGE_TYPE_PROPERTY) + || property.equals(JOptionPane.OPTION_TYPE_PROPERTY) + || property.equals(JOptionPane.OPTIONS_PROPERTY) + || property.equals(JOptionPane.WANTS_INPUT_PROPERTY)) + { + uninstallComponents(); + installComponents(); + optionPane.validate(); + } + } + } + + /** + * The minimum width for JOptionPanes. + */ + public static final int MinimumWidth = 262; + + /** + * The minimum height for JOptionPanes. + */ + public static final int MinimumHeight = 90; + + /** Whether the JOptionPane contains custom components. */ + protected boolean hasCustomComponents; + + // The initialFocusComponent seems to always be set to a button (even if + // I try to set initialSelectionValue). This is different from what the + // javadocs state (which should switch this reference to the input component + // if one is present since that is what's going to get focus). + + /** + * The button that will receive focus based on initialValue when no input + * component is present. If an input component is present, then the input + * component will receive focus instead. + */ + protected Component initialFocusComponent; + + /** The component that receives input when the JOptionPane needs it. */ + protected JComponent inputComponent; + + /** The minimum dimensions of the JOptionPane. */ + protected Dimension minimumSize; + + /** The propertyChangeListener for the JOptionPane. */ + protected PropertyChangeListener propertyChangeListener; + + /** The JOptionPane this UI delegate is used for. */ + protected JOptionPane optionPane; + + /** The size of the icons. */ + private static final int ICON_SIZE = 36; + + /** The string used to describe OK buttons. */ + private static final String OK_STRING = "OK"; + + /** The string used to describe Yes buttons. */ + private static final String YES_STRING = "Yes"; + + /** The string used to describe No buttons. */ + private static final String NO_STRING = "No"; + + /** The string used to describe Cancel buttons. */ + private static final String CANCEL_STRING = "Cancel"; + + /** The container for the message area. + * This is package-private to avoid an accessor method. */ + transient Container messageAreaContainer; + + /** The container for the buttons. + * This is package-private to avoid an accessor method. */ + transient Container buttonContainer; + + /** + * A helper class that implements Icon. This is used temporarily until + * ImageIcons are fixed. + */ + private static class MessageIcon implements Icon + { + /** + * This method returns the width of the icon. + * + * @return The width of the icon. + */ + public int getIconWidth() + { + return ICON_SIZE; + } + + /** + * This method returns the height of the icon. + * + * @return The height of the icon. + */ + public int getIconHeight() + { + return ICON_SIZE; + } + + /** + * This method paints the icon as a part of the given component using the + * given graphics and the given x and y position. + * + * @param c The component that owns this icon. + * @param g The Graphics object to paint with. + * @param x The x coordinate. + * @param y The y coordinate. + */ + public void paintIcon(Component c, Graphics g, int x, int y) + { + // Nothing to do here. + } + } + + /** The icon displayed for ERROR_MESSAGE. */ + private static MessageIcon errorIcon = new MessageIcon() + { + public void paintIcon(Component c, Graphics g, int x, int y) + { + Polygon oct = new Polygon(new int[] { 0, 0, 9, 27, 36, 36, 27, 9 }, + new int[] { 9, 27, 36, 36, 27, 9, 0, 0 }, 8); + g.translate(x, y); + + Color saved = g.getColor(); + g.setColor(Color.RED); + + g.fillPolygon(oct); + + g.setColor(Color.BLACK); + g.drawRect(13, 16, 10, 4); + + g.setColor(saved); + g.translate(-x, -y); + } + }; + + /** The icon displayed for INFORMATION_MESSAGE. */ + private static MessageIcon infoIcon = new MessageIcon() + { + public void paintIcon(Component c, Graphics g, int x, int y) + { + g.translate(x, y); + Color saved = g.getColor(); + + // Should be purple. + g.setColor(Color.RED); + + g.fillOval(0, 0, ICON_SIZE, ICON_SIZE); + + g.setColor(Color.BLACK); + g.drawOval(16, 6, 4, 4); + + Polygon bottomI = new Polygon(new int[] { 15, 15, 13, 13, 23, 23, 21, 21 }, + new int[] { 12, 28, 28, 30, 30, 28, 28, 12 }, + 8); + g.drawPolygon(bottomI); + + g.setColor(saved); + g.translate(-x, -y); + } + }; + + /** The icon displayed for WARNING_MESSAGE. */ + private static MessageIcon warningIcon = new MessageIcon() + { + public void paintIcon(Component c, Graphics g, int x, int y) + { + g.translate(x, y); + Color saved = g.getColor(); + g.setColor(Color.YELLOW); + + Polygon triangle = new Polygon(new int[] { 0, 18, 36 }, + new int[] { 36, 0, 36 }, 3); + g.fillPolygon(triangle); + + g.setColor(Color.BLACK); + + Polygon excl = new Polygon(new int[] { 15, 16, 20, 21 }, + new int[] { 8, 26, 26, 8 }, 4); + g.drawPolygon(excl); + g.drawOval(16, 30, 4, 4); + + g.setColor(saved); + g.translate(-x, -y); + } + }; + + /** The icon displayed for MESSAGE_ICON. */ + private static MessageIcon questionIcon = new MessageIcon() + { + public void paintIcon(Component c, Graphics g, int x, int y) + { + g.translate(x, y); + Color saved = g.getColor(); + g.setColor(Color.GREEN); + + g.fillRect(0, 0, ICON_SIZE, ICON_SIZE); + + g.setColor(Color.BLACK); + + g.drawOval(11, 2, 16, 16); + g.drawOval(14, 5, 10, 10); + + g.setColor(Color.GREEN); + g.fillRect(0, 10, ICON_SIZE, ICON_SIZE - 10); + + g.setColor(Color.BLACK); + + g.drawLine(11, 10, 14, 10); + + g.drawLine(24, 10, 17, 22); + g.drawLine(27, 10, 20, 22); + g.drawLine(17, 22, 20, 22); + + g.drawOval(17, 25, 3, 3); + + g.setColor(saved); + g.translate(-x, -y); + } + }; + + /** + * Creates a new BasicOptionPaneUI object. + */ + public BasicOptionPaneUI() + { + // Nothing to do here. + } + + /** + * This method is messaged to add the buttons to the given container. + * + * @param container The container to add components to. + * @param buttons The buttons to add. (If it is an instance of component, + * the Object is added directly. If it is an instance of Icon, it is + * packed into a label and added. For all other cases, the string + * representation of the Object is retreived and packed into a + * label.) + * @param initialIndex The index of the component that is the initialValue. + */ + protected void addButtonComponents(Container container, Object[] buttons, + int initialIndex) + { + if (buttons == null) + return; + for (int i = 0; i < buttons.length; i++) + { + if (buttons[i] != null) + { + Component toAdd; + if (buttons[i] instanceof Component) + toAdd = (Component) buttons[i]; + else + { + if (buttons[i] instanceof Icon) + toAdd = new JButton((Icon) buttons[i]); + else + toAdd = new JButton(buttons[i].toString()); + hasCustomComponents = true; + } + if (toAdd instanceof JButton) + ((JButton) toAdd).addActionListener(createButtonActionListener(i)); + if (i == initialIndex) + initialFocusComponent = toAdd; + container.add(toAdd); + } + } + selectInitialValue(optionPane); + } + + /** + * This method adds the appropriate icon the given container. + * + * @param top The container to add an icon to. + */ + protected void addIcon(Container top) + { + JLabel iconLabel = null; + Icon icon = getIcon(); + if (icon != null) + { + iconLabel = new JLabel(icon); + configureLabel(iconLabel); + top.add(iconLabel, BorderLayout.WEST); + } + } + + /** + * A helper method that returns an instance of GridBagConstraints to be used + * for creating the message area. + * + * @return An instance of GridBagConstraints. + */ + private static GridBagConstraints createConstraints() + { + GridBagConstraints constraints = new GridBagConstraints(); + constraints.gridx = GridBagConstraints.REMAINDER; + constraints.gridy = GridBagConstraints.REMAINDER; + constraints.gridwidth = 0; + constraints.anchor = GridBagConstraints.LINE_START; + constraints.fill = GridBagConstraints.NONE; + constraints.insets = new Insets(0, 0, 3, 0); + + return constraints; + } + + /** + * This method creates the proper object (if necessary) to represent msg. + * (If msg is an instance of Component, it will add it directly. If it is + * an icon, then it will pack it in a label and add it. Otherwise, it gets + * treated as a string. If the string is longer than maxll, a box is + * created and the burstStringInto is called with the box as the container. + * The box is then added to the given container. Otherwise, the string is + * packed in a label and placed in the given container.) This method is + * also used for adding the inputComponent to the container. + * + * @param container The container to add to. + * @param cons The constraints when adding. + * @param msg The message to add. + * @param maxll The max line length. + * @param internallyCreated Whether the msg is internally created. + */ + protected void addMessageComponents(Container container, + GridBagConstraints cons, Object msg, + int maxll, boolean internallyCreated) + { + if (msg == null) + return; + hasCustomComponents = internallyCreated; + if (msg instanceof Object[]) + { + Object[] arr = (Object[]) msg; + for (int i = 0; i < arr.length; i++) + addMessageComponents(container, cons, arr[i], maxll, + internallyCreated); + return; + } + else if (msg instanceof Component) + { + container.add((Component) msg, cons); + cons.gridy++; + } + else if (msg instanceof Icon) + { + JLabel label = new JLabel((Icon) msg); + configureLabel(label); + container.add(label, cons); + cons.gridy++; + } + else + { + // Undocumented behaviour. + // if msg.toString().length greater than maxll + // it will create a box and burst the string. + // otherwise, it will just create a label and re-call + // this method with the label o.O + if (msg.toString().length() > maxll || msg.toString().contains("\n")) + { + Box tmp = new Box(BoxLayout.Y_AXIS); + burstStringInto(tmp, msg.toString(), maxll); + addMessageComponents(container, cons, tmp, maxll, true); + } + else + { + JLabel label = new JLabel(msg.toString()); + configureLabel(label); + addMessageComponents(container, cons, label, maxll, true); + } + } + } + + /** + * This method creates instances of d (recursively if necessary based on + * maxll) and adds to c. + * + * @param c The container to add to. + * @param d The string to burst. + * @param maxll The max line length. + */ + protected void burstStringInto(Container c, String d, int maxll) + { + if (d == null || c == null) + return; + + int newlineIndex = d.indexOf('\n'); + String line; + String remainder; + if (newlineIndex >= 0 && newlineIndex < maxll) + { + line = d.substring(0, newlineIndex); + remainder = d.substring(newlineIndex + 1); + } + else + { + line = d.substring(0, maxll); + remainder = d.substring(maxll); + } + JLabel label = new JLabel(line); + configureLabel(label); + c.add(label); + + // If there is nothing left to burst, then we can stop. + if (remainder.length() == 0) + return; + + // Recursively call ourselves to burst the remainder of the string, + if (remainder.length() > maxll || remainder.contains("\n")) + burstStringInto(c, remainder, maxll); + else + { + // Add the remainder to the container and be done. + JLabel l = new JLabel(remainder); + configureLabel(l); + c.add(l); + } + } + + /** + * This method returns true if the given JOptionPane contains custom + * components. + * + * @param op The JOptionPane to check. + * + * @return True if the JOptionPane contains custom components. + */ + public boolean containsCustomComponents(JOptionPane op) + { + return hasCustomComponents; + } + + /** + * This method creates a button action listener for the given button index. + * + * @param buttonIndex The index of the button in components. + * + * @return A new ButtonActionListener. + */ + protected ActionListener createButtonActionListener(int buttonIndex) + { + return new ButtonActionListener(buttonIndex); + } + + /** + * This method creates the button area. + * + * @return A new Button Area. + */ + protected Container createButtonArea() + { + JPanel buttonPanel = new JPanel(); + Border b = UIManager.getBorder("OptionPane.buttonAreaBorder"); + if (b != null) + buttonPanel.setBorder(b); + + buttonPanel.setLayout(createLayoutManager()); + addButtonComponents(buttonPanel, getButtons(), getInitialValueIndex()); + + return buttonPanel; + } + + /** + * This method creates a new LayoutManager for the button area. + * + * @return A new LayoutManager for the button area. + */ + protected LayoutManager createLayoutManager() + { + return new ButtonAreaLayout(getSizeButtonsToSameWidth(), 6); + } + + /** + * This method creates the message area. + * + * @return A new message area. + */ + protected Container createMessageArea() + { + JPanel messageArea = new JPanel(); + Border messageBorder = UIManager.getBorder("OptionPane.messageAreaBorder"); + if (messageBorder != null) + messageArea.setBorder(messageBorder); + + messageArea.setLayout(new BorderLayout()); + addIcon(messageArea); + + JPanel rightSide = new JPanel(); + rightSide.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); + rightSide.setLayout(new GridBagLayout()); + GridBagConstraints con = createConstraints(); + + addMessageComponents(rightSide, con, getMessage(), + getMaxCharactersPerLineCount(), false); + + if (optionPane.getWantsInput()) + { + Object[] selection = optionPane.getSelectionValues(); + + if (selection == null) + inputComponent = new JTextField(15); + else if (selection.length < 20) + inputComponent = new JComboBox(selection); + else + inputComponent = new JList(selection); + if (inputComponent != null) + { + addMessageComponents(rightSide, con, inputComponent, + getMaxCharactersPerLineCount(), false); + resetSelectedValue(); + selectInitialValue(optionPane); + } + } + + messageArea.add(rightSide, BorderLayout.CENTER); + + return messageArea; + } + + /** + * This method creates a new PropertyChangeListener for listening to the + * JOptionPane. + * + * @return A new PropertyChangeListener. + */ + protected PropertyChangeListener createPropertyChangeListener() + { + return new PropertyChangeHandler(); + } + + /** + * This method creates a Container that will separate the message and button + * areas. + * + * @return A Container that will separate the message and button areas. + */ + protected Container createSeparator() + { + // The reference implementation returns null here. When overriding + // to return something non-null, the component gets added between + // the message area and the button area. See installComponents(). + return null; + } + + /** + * This method creates a new BasicOptionPaneUI for the given component. + * + * @param x The component to create a UI for. + * + * @return A new BasicOptionPaneUI. + */ + public static ComponentUI createUI(JComponent x) + { + return new BasicOptionPaneUI(); + } + + /** + * This method returns the buttons for the JOptionPane. If no options are + * set, a set of options will be created based upon the optionType. + * + * @return The buttons that will be added. + */ + protected Object[] getButtons() + { + if (optionPane.getOptions() != null) + return optionPane.getOptions(); + switch (optionPane.getOptionType()) + { + case JOptionPane.YES_NO_OPTION: + return new Object[] { YES_STRING, NO_STRING }; + case JOptionPane.YES_NO_CANCEL_OPTION: + return new Object[] { YES_STRING, NO_STRING, CANCEL_STRING }; + case JOptionPane.OK_CANCEL_OPTION: + return new Object[] { OK_STRING, CANCEL_STRING }; + case JOptionPane.DEFAULT_OPTION: + return (optionPane.getWantsInput()) ? + new Object[] { OK_STRING, CANCEL_STRING } : + (optionPane.getMessageType() == JOptionPane.QUESTION_MESSAGE) ? + new Object[] { YES_STRING, NO_STRING, CANCEL_STRING } : + // ERROR_MESSAGE, INFORMATION_MESSAGE, WARNING_MESSAGE, PLAIN_MESSAGE + new Object[] { OK_STRING }; + } + return null; + } + + /** + * This method will return the icon the user has set or the icon that will + * be used based on message type. + * + * @return The icon to use in the JOptionPane. + */ + protected Icon getIcon() + { + if (optionPane.getIcon() != null) + return optionPane.getIcon(); + else + return getIconForType(optionPane.getMessageType()); + } + + /** + * This method returns the icon for the given messageType. + * + * @param messageType The type of message. + * + * @return The icon for the given messageType. + */ + protected Icon getIconForType(int messageType) + { + Icon tmp = null; + switch (messageType) + { + case JOptionPane.ERROR_MESSAGE: + tmp = errorIcon; + break; + case JOptionPane.INFORMATION_MESSAGE: + tmp = infoIcon; + break; + case JOptionPane.WARNING_MESSAGE: + tmp = warningIcon; + break; + case JOptionPane.QUESTION_MESSAGE: + tmp = questionIcon; + break; + } + return tmp; + // FIXME: Don't cast till the default icons are in. + // return new IconUIResource(tmp); + } + + /** + * This method returns the index of the initialValue in the options array. + * + * @return The index of the initalValue. + */ + protected int getInitialValueIndex() + { + Object[] buttons = getButtons(); + + if (buttons == null) + return -1; + + Object select = optionPane.getInitialValue(); + + for (int i = 0; i < buttons.length; i++) + { + if (select == buttons[i]) + return i; + } + return 0; + } + + /** + * This method returns the maximum number of characters that should be + * placed on a line. + * + * @return The maximum number of characteres that should be placed on a + * line. + */ + protected int getMaxCharactersPerLineCount() + { + return optionPane.getMaxCharactersPerLineCount(); + } + + /** + * This method returns the maximum size. + * + * @param c The JComponent to measure. + * + * @return The maximum size. + */ + public Dimension getMaximumSize(JComponent c) + { + return getPreferredSize(c); + } + + /** + * This method returns the message of the JOptionPane. + * + * @return The message. + */ + protected Object getMessage() + { + return optionPane.getMessage(); + } + + /** + * This method returns the minimum size of the JOptionPane. + * + * @return The minimum size. + */ + public Dimension getMinimumOptionPaneSize() + { + return minimumSize; + } + + /** + * This method returns the minimum size. + * + * @param c The JComponent to measure. + * + * @return The minimum size. + */ + public Dimension getMinimumSize(JComponent c) + { + return getPreferredSize(c); + } + + /** + * This method returns the preferred size of the JOptionPane. The preferred + * size is the maximum of the size desired by the layout and the minimum + * size. + * + * @param c The JComponent to measure. + * + * @return The preferred size. + */ + public Dimension getPreferredSize(JComponent c) + { + Dimension d = optionPane.getLayout().preferredLayoutSize(optionPane); + Dimension d2 = getMinimumOptionPaneSize(); + + int w = Math.max(d.width, d2.width); + int h = Math.max(d.height, d2.height); + return new Dimension(w, h); + } + + /** + * This method returns whether all buttons should have the same width. + * + * @return Whether all buttons should have the same width. + */ + protected boolean getSizeButtonsToSameWidth() + { + return true; + } + + /** + * This method installs components for the JOptionPane. + */ + protected void installComponents() + { + // First thing is the message area. + optionPane.add(createMessageArea()); + + // Add separator when createSeparator() is overridden to return + // something other than null. + Container sep = createSeparator(); + if (sep != null) + optionPane.add(sep); + + // Last thing is the button area. + optionPane.add(createButtonArea()); + } + + /** + * This method installs defaults for the JOptionPane. + */ + protected void installDefaults() + { + LookAndFeel.installColorsAndFont(optionPane, "OptionPane.background", + "OptionPane.foreground", + "OptionPane.font"); + LookAndFeel.installBorder(optionPane, "OptionPane.border"); + optionPane.setOpaque(true); + + minimumSize = UIManager.getDimension("OptionPane.minimumSize"); + + // FIXME: Image icons don't seem to work properly right now. + // Once they do, replace the synthetic icons with these ones. + + /* + warningIcon = (IconUIResource) defaults.getIcon("OptionPane.warningIcon"); + infoIcon = (IconUIResource) defaults.getIcon("OptionPane.informationIcon"); + errorIcon = (IconUIResource) defaults.getIcon("OptionPane.errorIcon"); + questionIcon = (IconUIResource) defaults.getIcon("OptionPane.questionIcon"); + */ + } + + /** + * This method installs keyboard actions for the JOptionpane. + */ + protected void installKeyboardActions() + { + // Install the input map. + Object[] bindings = + (Object[]) SharedUIDefaults.get("OptionPane.windowBindings"); + InputMap inputMap = LookAndFeel.makeComponentInputMap(optionPane, + bindings); + SwingUtilities.replaceUIInputMap(optionPane, + JComponent.WHEN_IN_FOCUSED_WINDOW, + inputMap); + + // FIXME: The JDK uses a LazyActionMap for parentActionMap + SwingUtilities.replaceUIActionMap(optionPane, getActionMap()); + } + + /** + * Fetches the action map from the UI defaults, or create a new one + * if the action map hasn't been initialized. + * + * @return the action map + */ + private ActionMap getActionMap() + { + ActionMap am = (ActionMap) UIManager.get("OptionPane.actionMap"); + if (am == null) + { + am = createDefaultActions(); + UIManager.getLookAndFeelDefaults().put("OptionPane.actionMap", am); + } + return am; + } + + private ActionMap createDefaultActions() + { + ActionMapUIResource am = new ActionMapUIResource(); + Action action = new OptionPaneCloseAction(); + + am.put("close", action); + return am; + } + + /** + * This method installs listeners for the JOptionPane. + */ + protected void installListeners() + { + propertyChangeListener = createPropertyChangeListener(); + + optionPane.addPropertyChangeListener(propertyChangeListener); + } + + /** + * This method installs the UI for the JOptionPane. + * + * @param c The JComponent to install the UI for. + */ + public void installUI(JComponent c) + { + if (c instanceof JOptionPane) + { + optionPane = (JOptionPane) c; + + installDefaults(); + installComponents(); + installListeners(); + installKeyboardActions(); + } + } + + /** + * Changes the inputValue property in the JOptionPane based on the current + * value of the inputComponent. + */ + protected void resetInputValue() + { + if (optionPane.getWantsInput() && inputComponent != null) + { + Object output = null; + if (inputComponent instanceof JTextField) + output = ((JTextField) inputComponent).getText(); + else if (inputComponent instanceof JComboBox) + output = ((JComboBox) inputComponent).getSelectedItem(); + else if (inputComponent instanceof JList) + output = ((JList) inputComponent).getSelectedValue(); + + if (output != null) + optionPane.setInputValue(output); + } + } + + /** + * This method requests focus to the inputComponent (if one is present) and + * the initialFocusComponent otherwise. + * + * @param op The JOptionPane. + */ + public void selectInitialValue(JOptionPane op) + { + if (inputComponent != null) + { + inputComponent.requestFocus(); + return; + } + if (initialFocusComponent != null) + initialFocusComponent.requestFocus(); + } + + /** + * This method resets the value in the inputComponent to the + * initialSelectionValue property. + * This is package-private to avoid an accessor method. + */ + void resetSelectedValue() + { + if (inputComponent != null) + { + Object init = optionPane.getInitialSelectionValue(); + if (init == null) + return; + if (inputComponent instanceof JTextField) + ((JTextField) inputComponent).setText((String) init); + else if (inputComponent instanceof JComboBox) + ((JComboBox) inputComponent).setSelectedItem(init); + else if (inputComponent instanceof JList) + { + // ((JList) inputComponent).setSelectedValue(init, true); + } + } + } + + /** + * This method uninstalls all the components in the JOptionPane. + */ + protected void uninstallComponents() + { + optionPane.removeAll(); + buttonContainer = null; + messageAreaContainer = null; + } + + /** + * This method uninstalls the defaults for the JOptionPane. + */ + protected void uninstallDefaults() + { + optionPane.setFont(null); + optionPane.setForeground(null); + optionPane.setBackground(null); + + minimumSize = null; + + // FIXME: ImageIcons don't seem to work properly + + /* + warningIcon = null; + errorIcon = null; + questionIcon = null; + infoIcon = null; + */ + } + + /** + * This method uninstalls keyboard actions for the JOptionPane. + */ + protected void uninstallKeyboardActions() + { + SwingUtilities.replaceUIInputMap(optionPane, JComponent. + WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, null); + SwingUtilities.replaceUIActionMap(optionPane, null); + } + + /** + * This method uninstalls listeners for the JOptionPane. + */ + protected void uninstallListeners() + { + optionPane.removePropertyChangeListener(propertyChangeListener); + propertyChangeListener = null; + } + + /** + * This method uninstalls the UI for the given JComponent. + * + * @param c The JComponent to uninstall for. + */ + public void uninstallUI(JComponent c) + { + uninstallKeyboardActions(); + uninstallListeners(); + uninstallComponents(); + uninstallDefaults(); + + optionPane = null; + } + + /** + * Applies the proper UI configuration to labels that are added to + * the OptionPane. + * + * @param l the label to configure + */ + private void configureLabel(JLabel l) + { + Color c = UIManager.getColor("OptionPane.messageForeground"); + if (c != null) + l.setForeground(c); + Font f = UIManager.getFont("OptionPane.messageFont"); + if (f != null) + l.setFont(f); + } +} diff --git a/libjava/classpath/javax/swing/plaf/basic/BasicPanelUI.java b/libjava/classpath/javax/swing/plaf/basic/BasicPanelUI.java new file mode 100644 index 000000000..959462a78 --- /dev/null +++ b/libjava/classpath/javax/swing/plaf/basic/BasicPanelUI.java @@ -0,0 +1,132 @@ +/* BasicPanelUI.java + Copyright (C) 2002, 2004, 2006, 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 javax.swing.JComponent; +import javax.swing.JPanel; +import javax.swing.LookAndFeel; +import javax.swing.plaf.ComponentUI; +import javax.swing.plaf.PanelUI; + +/** + * A UI delegate for the {@link JPanel} component. + */ +public class BasicPanelUI extends PanelUI +{ + /** + * A UI delegate that can be shared by all panels (because the delegate is + * stateless). + */ + static BasicPanelUI sharedUI; + + /** + * Returns a UI delegate for the specified component. + * + * @param panel the panel. + */ + public static ComponentUI createUI(JComponent panel) + { + if (sharedUI == null) + sharedUI = new BasicPanelUI(); + return sharedUI; + } + + /** + * Installs this UI delegate in the specified component. + * + * @param c the component (should be a {@link JPanel}, <code>null</code> not + * permitted). + */ + public void installUI(JComponent c) + { + super.installUI(c); + if (c instanceof JPanel) + { + JPanel p = (JPanel) c; + installDefaults(p); + } + } + + /** + * Installs the defaults for this UI delegate in the specified panel. + * + * @param p the panel (<code>null</code> not permitted). + */ + protected void installDefaults(JPanel p) + { + LookAndFeel.installColorsAndFont(p, "Panel.background", "Panel.foreground", + "Panel.font"); + + // A test against the reference implementation shows that this method will + // install a border if one is defined in the UIDefaults table (even though + // the BasicLookAndFeel doesn't actually define a "Panel.border"). This + // test was written after discovering that a null argument to + // uninstallDefaults throws a NullPointerException in + // LookAndFeel.uninstallBorder()... + LookAndFeel.installBorder(p, "Panel.border"); + } + + /** + * Uninstalls this UI delegate from the specified component. + * + * @param c the component (<code>null</code> not permitted). + */ + public void uninstallUI(JComponent c) + { + uninstallDefaults((JPanel) c); + } + + /** + * Uninstalls the UI defaults for the specified panel. + * + * @param p the panel (<code>null</code> not permitted). + */ + protected void uninstallDefaults(JPanel p) + { + // Tests on the reference implementation showed this method: + // (1) doesn't actually remove the installed colors and font installed + // by installDefaults(), it isn't necessary; + // (2) throws a NullPointerException in LookAndFeel.uninstallBorder() if + // p is null. Strangely, no border is installed by the + // BasicLookAndFeel - perhaps this is needed by another LAF? + + LookAndFeel.uninstallBorder(p); + } + +} diff --git a/libjava/classpath/javax/swing/plaf/basic/BasicPasswordFieldUI.java b/libjava/classpath/javax/swing/plaf/basic/BasicPasswordFieldUI.java new file mode 100644 index 000000000..cc839179f --- /dev/null +++ b/libjava/classpath/javax/swing/plaf/basic/BasicPasswordFieldUI.java @@ -0,0 +1,74 @@ +/* BasicPasswordFieldUI.java + Copyright (C) 2004 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 javax.swing.JComponent; +import javax.swing.UIDefaults; +import javax.swing.plaf.ComponentUI; +import javax.swing.text.Element; +import javax.swing.text.PasswordView; +import javax.swing.text.View; + +public class BasicPasswordFieldUI extends BasicTextFieldUI +{ + public BasicPasswordFieldUI() + { + // Nothing to do here. + } + + public View create(Element elem) + { + return new PasswordView(elem); + } + + public static ComponentUI createUI(JComponent c) + { + return new BasicPasswordFieldUI(); + } + + /** + * Returns the prefix for entries in the {@link UIDefaults} table. + * + * @return "PasswordField" + */ + protected String getPropertyPrefix() + { + return "PasswordField"; + } +} diff --git a/libjava/classpath/javax/swing/plaf/basic/BasicPopupMenuSeparatorUI.java b/libjava/classpath/javax/swing/plaf/basic/BasicPopupMenuSeparatorUI.java new file mode 100644 index 000000000..b62947722 --- /dev/null +++ b/libjava/classpath/javax/swing/plaf/basic/BasicPopupMenuSeparatorUI.java @@ -0,0 +1,114 @@ +/* BasicPopupMenuSeparatorUI.java -- + Copyright (C) 2004 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 java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Rectangle; + +import javax.swing.JComponent; +import javax.swing.JPopupMenu; +import javax.swing.SwingUtilities; +import javax.swing.plaf.ComponentUI; + +/** + * The Basic Look and Feel UI delegate for JPopupMenu.Separator. + */ +public class BasicPopupMenuSeparatorUI extends BasicSeparatorUI +{ + /** + * Creates a new BasicPopupMenuSeparatorUI object. + */ + public BasicPopupMenuSeparatorUI() + { + super(); + } + + /** + * Creates a new UI delegate for the given JComponent. + * + * @param c The JComponent to create a delegate for. + * + * @return A new BasicPopupMenuSeparatorUI + */ + public static ComponentUI createUI(JComponent c) + { + return new BasicPopupMenuSeparatorUI(); + } + + /** + * The Popup Menu Separator has two lines. The top line will be + * painted using highlight color and the bottom using shadow color. + * + * @param g The Graphics object to paint with + * @param c The JComponent to paint. + */ + public void paint(Graphics g, JComponent c) + { + if (! (c instanceof JPopupMenu.Separator)) + return; + + Rectangle r = new Rectangle(); + SwingUtilities.calculateInnerArea(c, r); + Color saved = g.getColor(); + + int midAB = r.width / 2 + r.x; + int midAD = r.height / 2 + r.y; + + g.setColor(highlight); + g.drawLine(r.x, midAD, r.x + r.width, midAD); + + g.setColor(shadow); + g.drawLine(r.x, midAD + 1, r.x + r.width, midAD + 1); + } + + /** + * This method returns the preferred size of the + * JComponent. + * + * @param c The JComponent to measure. + * + * @return The preferred size. + */ + public Dimension getPreferredSize(JComponent c) + { + return super.getPreferredSize(c); + } +} diff --git a/libjava/classpath/javax/swing/plaf/basic/BasicPopupMenuUI.java b/libjava/classpath/javax/swing/plaf/basic/BasicPopupMenuUI.java new file mode 100644 index 000000000..6cd433b33 --- /dev/null +++ b/libjava/classpath/javax/swing/plaf/basic/BasicPopupMenuUI.java @@ -0,0 +1,1019 @@ +/* BasicPopupMenuUI.java + Copyright (C) 2002, 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 java.awt.Component; +import java.awt.Dimension; +import java.awt.KeyboardFocusManager; +import java.awt.event.ActionEvent; +import java.awt.event.ComponentEvent; +import java.awt.event.ComponentListener; +import java.awt.event.MouseEvent; +import java.util.EventListener; + +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.ActionMap; +import javax.swing.BoxLayout; +import javax.swing.InputMap; +import javax.swing.JApplet; +import javax.swing.JComponent; +import javax.swing.JFrame; +import javax.swing.JMenu; +import javax.swing.JMenuBar; +import javax.swing.JMenuItem; +import javax.swing.JPopupMenu; +import javax.swing.JRootPane; +import javax.swing.LookAndFeel; +import javax.swing.MenuElement; +import javax.swing.MenuSelectionManager; +import javax.swing.SwingUtilities; +import javax.swing.UIManager; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import javax.swing.event.PopupMenuEvent; +import javax.swing.event.PopupMenuListener; +import javax.swing.plaf.ActionMapUIResource; +import javax.swing.plaf.ComponentUI; +import javax.swing.plaf.PopupMenuUI; + +/** + * UI Delegate for JPopupMenu + */ +public class BasicPopupMenuUI extends PopupMenuUI +{ + /** + * Handles keyboard navigation through menus. + */ + private static class NavigateAction + extends AbstractAction + { + + /** + * Creates a new NavigateAction instance. + * + * @param name the name of the action + */ + NavigateAction(String name) + { + super(name); + } + + /** + * Actually performs the action. + */ + public void actionPerformed(ActionEvent event) + { + String name = (String) getValue(Action.NAME); + if (name.equals("selectNext")) + navigateNextPrevious(true); + else if (name.equals("selectPrevious")) + navigateNextPrevious(false); + else if (name.equals("selectChild")) + navigateParentChild(true); + else if (name.equals("selectParent")) + navigateParentChild(false); + else if (name.equals("cancel")) + cancel(); + else if (name.equals("return")) + doReturn(); + else + assert false : "Must not reach here"; + } + + /** + * Navigates to the next or previous menu item. + * + * @param dir <code>true</code>: navigate to next, <code>false</code>: + * navigate to previous + */ + private void navigateNextPrevious(boolean dir) + { + MenuSelectionManager msm = MenuSelectionManager.defaultManager(); + MenuElement path[] = msm.getSelectedPath(); + int len = path.length; + if (len >= 2) + { + + if (path[0] instanceof JMenuBar && + path[1] instanceof JMenu && len == 2) + { + + // A toplevel menu is selected, but its popup not yet shown. + // Show the popup and select the first item + JPopupMenu popup = ((JMenu)path[1]).getPopupMenu(); + MenuElement next = + findEnabledChild(popup.getSubElements(), -1, true); + MenuElement[] newPath; + + if (next != null) + { + newPath = new MenuElement[4]; + newPath[3] = next; + } + else + { + // Menu has no enabled items, show the popup anyway. + newPath = new MenuElement[3]; + } + System.arraycopy(path, 0, newPath, 0, 2); + newPath[2] = popup; + msm.setSelectedPath(newPath); + } + else if (path[len - 1] instanceof JPopupMenu && + path[len - 2] instanceof JMenu) + { + // Select next item in already shown popup menu. + JMenu menu = (JMenu) path[len - 2]; + JPopupMenu popup = menu.getPopupMenu(); + MenuElement next = + findEnabledChild(popup.getSubElements(), -1, dir); + + if (next != null) + { + MenuElement[] newPath = new MenuElement[len + 1]; + System.arraycopy(path, 0, newPath, 0, len); + newPath[len] = next; + msm.setSelectedPath(newPath); + } + else + { + // All items in the popup are disabled. + // Find the parent popup menu and select + // its next item. If there's no parent popup menu , do nothing. + if (len > 2 && path[len - 3] instanceof JPopupMenu) + { + popup = ((JPopupMenu) path[len - 3]); + next = findEnabledChild(popup.getSubElements(), + menu, dir); + if (next != null && next != menu) + { + MenuElement[] newPath = new MenuElement[len - 1]; + System.arraycopy(path, 0, newPath, 0, len - 2); + newPath[len - 2] = next; + msm.setSelectedPath(newPath); + } + } + } + } + else + { + // Only select the next item. + MenuElement subs[] = path[len - 2].getSubElements(); + MenuElement nextChild = + findEnabledChild(subs, path[len - 1], dir); + if (nextChild == null) + { + nextChild = findEnabledChild(subs, -1, dir); + } + if (nextChild != null) + { + path[len-1] = nextChild; + msm.setSelectedPath(path); + } + } + } + } + + private MenuElement findEnabledChild(MenuElement[] children, + MenuElement start, boolean dir) + { + MenuElement found = null; + for (int i = 0; i < children.length && found == null; i++) + { + if (children[i] == start) + { + found = findEnabledChild(children, i, dir); + } + } + return found; + } + + /** + * Searches the next or previous enabled child menu element. + * + * @param children the children to search through + * @param start the index at which to start + * @param dir the direction (true == forward, false == backward) + * + * @return the found element or null + */ + private MenuElement findEnabledChild(MenuElement[] children, + int start, boolean dir) + { + MenuElement result = null; + if (dir) + { + result = findNextEnabledChild(children, start + 1, children.length-1); + if (result == null) + result = findNextEnabledChild(children, 0, start - 1); + } + else + { + result = findPreviousEnabledChild(children, start - 1, 0); + if (result == null) + result = findPreviousEnabledChild(children, children.length-1, + start + 1); + } + return result; + } + + /** + * Finds the next child element that is enabled and visible. + * + * @param children the children to search through + * @param start the start index + * @param end the end index + * + * @return the found child, or null + */ + private MenuElement findNextEnabledChild(MenuElement[] children, int start, + int end) + { + MenuElement found = null; + for (int i = start; i <= end && found == null; i++) + { + if (children[i] != null) + { + Component comp = children[i].getComponent(); + if (comp != null && comp.isEnabled() && comp.isVisible()) + { + found = children[i]; + } + } + } + return found; + } + + /** + * Finds the previous child element that is enabled and visible. + * + * @param children the children to search through + * @param start the start index + * @param end the end index + * + * @return the found child, or null + */ + private MenuElement findPreviousEnabledChild(MenuElement[] children, + int start, int end) + { + MenuElement found = null; + for (int i = start; i >= end && found == null; i--) + { + if (children[i] != null) + { + Component comp = children[i].getComponent(); + if (comp != null && comp.isEnabled() && comp.isVisible()) + { + found = children[i]; + } + } + } + return found; + } + + /** + * Navigates to the parent or child menu item. + * + * @param selectChild <code>true</code>: navigate to child, + * <code>false</code>: navigate to parent + */ + private void navigateParentChild(boolean selectChild) + { + MenuSelectionManager msm = MenuSelectionManager.defaultManager(); + MenuElement path[] = msm.getSelectedPath(); + int len = path.length; + + if (selectChild) + { + if (len > 0 && path[len - 1] instanceof JMenu + && ! ((JMenu) path[len-1]).isTopLevelMenu()) + { + // We have a submenu, open it. + JMenu menu = (JMenu) path[len - 1]; + JPopupMenu popup = menu.getPopupMenu(); + MenuElement[] subs = popup.getSubElements(); + MenuElement item = findEnabledChild(subs, -1, true); + MenuElement[] newPath; + + if (item == null) + { + newPath = new MenuElement[len + 1]; + } + else + { + newPath = new MenuElement[len + 2]; + newPath[len + 1] = item; + } + System.arraycopy(path, 0, newPath, 0, len); + newPath[len] = popup; + msm.setSelectedPath(newPath); + return; + } + } + else + { + int popupIndex = len-1; + if (len > 2 + && (path[popupIndex] instanceof JPopupMenu + || path[--popupIndex] instanceof JPopupMenu) + && ! ((JMenu) path[popupIndex - 1]).isTopLevelMenu()) + { + // We have a submenu, close it. + MenuElement newPath[] = new MenuElement[popupIndex]; + System.arraycopy(path, 0, newPath, 0, popupIndex); + msm.setSelectedPath(newPath); + return; + } + } + + // If we got here, we have not selected a child or parent. + // Check if we have a toplevel menu selected. If so, then select + // another one. + if (len > 1 && path[0] instanceof JMenuBar) + { + MenuElement currentMenu = path[1]; + MenuElement nextMenu = findEnabledChild(path[0].getSubElements(), + currentMenu, selectChild); + + if (nextMenu != null && nextMenu != currentMenu) + { + MenuElement newSelection[]; + if (len == 2) + { + // Menu is selected but its popup not shown. + newSelection = new MenuElement[2]; + newSelection[0] = path[0]; + newSelection[1] = nextMenu; + } + else + { + // Menu is selected and its popup is shown. + newSelection = new MenuElement[3]; + newSelection[0] = path[0]; + newSelection[1] = nextMenu; + newSelection[2] = ((JMenu) nextMenu).getPopupMenu(); + } + msm.setSelectedPath(newSelection); + } + } + } + + /** + * Handles cancel requests (ESC key). + */ + private void cancel() + { + // Fire popup menu cancelled event. Unfortunately the + // firePopupMenuCancelled() is protected in JPopupMenu so we work + // around this limitation by fetching the listeners and notifying them + // directly. + JPopupMenu lastPopup = (JPopupMenu) getLastPopup(); + EventListener[] ll = lastPopup.getListeners(PopupMenuListener.class); + for (int i = 0; i < ll.length; i++) + { + PopupMenuEvent ev = new PopupMenuEvent(lastPopup); + ((PopupMenuListener) ll[i]).popupMenuCanceled(ev); + } + + // Close the last popup or the whole selection if there's only one + // popup left. + MenuSelectionManager msm = MenuSelectionManager.defaultManager(); + MenuElement path[] = msm.getSelectedPath(); + if(path.length > 4) + { + MenuElement newPath[] = new MenuElement[path.length - 2]; + System.arraycopy(path,0,newPath,0,path.length-2); + MenuSelectionManager.defaultManager().setSelectedPath(newPath); + } + else + msm.clearSelectedPath(); + } + + /** + * Returns the last popup menu in the current selection or null. + * + * @return the last popup menu in the current selection or null + */ + private JPopupMenu getLastPopup() + { + MenuSelectionManager msm = MenuSelectionManager.defaultManager(); + MenuElement[] p = msm.getSelectedPath(); + JPopupMenu popup = null; + for(int i = p.length - 1; popup == null && i >= 0; i--) + { + if (p[i] instanceof JPopupMenu) + popup = (JPopupMenu) p[i]; + } + return popup; + } + + /** + * Handles ENTER key requests. This normally opens submenus on JMenu + * items, or activates the menu item as if it's been clicked on it. + */ + private void doReturn() + { + KeyboardFocusManager fmgr = + KeyboardFocusManager.getCurrentKeyboardFocusManager(); + Component focusOwner = fmgr.getFocusOwner(); + if((focusOwner == null || (focusOwner instanceof JRootPane))) + { + MenuSelectionManager msm = MenuSelectionManager.defaultManager(); + MenuElement path[] = msm.getSelectedPath(); + MenuElement lastElement; + if(path.length > 0) + { + lastElement = path[path.length - 1]; + if(lastElement instanceof JMenu) + { + MenuElement newPath[] = new MenuElement[path.length + 1]; + System.arraycopy(path,0,newPath,0,path.length); + newPath[path.length] = ((JMenu) lastElement).getPopupMenu(); + msm.setSelectedPath(newPath); + } + else if(lastElement instanceof JMenuItem) + { + JMenuItem mi = (JMenuItem)lastElement; + if (mi.getUI() instanceof BasicMenuItemUI) + { + ((BasicMenuItemUI)mi.getUI()).doClick(msm); + } + else + { + msm.clearSelectedPath(); + mi.doClick(0); + } + } + } + } + } + } + + /** + * Installs keyboard actions when a popup is opened, and uninstalls the + * keyboard actions when closed. This listens on the default + * MenuSelectionManager. + */ + private class KeyboardHelper + implements ChangeListener + { + private MenuElement[] lastSelectedPath = new MenuElement[0]; + private Component lastFocused; + private JRootPane invokerRootPane; + + public void stateChanged(ChangeEvent event) + { + MenuSelectionManager msm = (MenuSelectionManager) event.getSource(); + MenuElement[] p = msm.getSelectedPath(); + JPopupMenu popup = getActivePopup(p); + if (popup == null || popup.isFocusable()) + { + if (lastSelectedPath.length != 0 && p.length != 0 ) + { + if (! invokerEquals(p[0], lastSelectedPath[0])) + { + uninstallKeyboardActionsImpl(); + lastSelectedPath = new MenuElement[0]; + } + } + + if (lastSelectedPath.length == 0 && p.length > 0) + { + JComponent invoker; + if (popup == null) + { + if (p.length == 2 && p[0] instanceof JMenuBar + && p[1] instanceof JMenu) + { + // A menu has been selected but not opened. + invoker = (JComponent)p[1]; + popup = ((JMenu)invoker).getPopupMenu(); + } + else + { + return; + } + } + else + { + Component c = popup.getInvoker(); + if(c instanceof JFrame) + { + invoker = ((JFrame) c).getRootPane(); + } + else if(c instanceof JApplet) + { + invoker = ((JApplet) c).getRootPane(); + } + else + { + while (!(c instanceof JComponent)) + { + if (c == null) + { + return; + } + c = c.getParent(); + } + invoker = (JComponent)c; + } + } + + // Remember current focus owner. + lastFocused = KeyboardFocusManager. + getCurrentKeyboardFocusManager().getFocusOwner(); + + // Install keybindings used for menu navigation. + invokerRootPane = SwingUtilities.getRootPane(invoker); + if (invokerRootPane != null) + { + invokerRootPane.requestFocus(true); + installKeyboardActionsImpl(); + } + } + else if (lastSelectedPath.length != 0 && p.length == 0) + { + // menu hidden -- return focus to where it had been before + // and uninstall menu keybindings + uninstallKeyboardActionsImpl(); + } + } + + // Remember the last path selected + lastSelectedPath = p; + } + + private JPopupMenu getActivePopup(MenuElement[] path) + { + JPopupMenu active = null; + for (int i = path.length - 1; i >= 0 && active == null; i--) + { + MenuElement elem = path[i]; + if (elem instanceof JPopupMenu) + { + active = (JPopupMenu) elem; + } + } + return active; + } + + private boolean invokerEquals(MenuElement el1, MenuElement el2) + { + Component invoker1 = el1.getComponent(); + Component invoker2 = el2.getComponent(); + if (invoker1 instanceof JPopupMenu) + invoker1 = ((JPopupMenu) invoker1).getInvoker(); + if (invoker2 instanceof JPopupMenu) + invoker2 = ((JPopupMenu) invoker2).getInvoker(); + return invoker1 == invoker2; + } + } + + /* popupMenu for which this UI delegate is for*/ + protected JPopupMenu popupMenu; + + /* PopupMenuListener listens to popup menu events fired by JPopupMenu*/ + private transient PopupMenuListener popupMenuListener; + + /* ComponentListener listening to popupMenu's invoker. + * This is package-private to avoid an accessor method. */ + TopWindowListener topWindowListener; + + /** + * Counts how many popup menus are handled by this UI or a subclass. + * This is used to install a KeyboardHelper on the MenuSelectionManager + * for the first popup, and uninstall this same KeyboardHelper when the + * last popup is uninstalled. + */ + private static int numPopups; + + /** + * This is the KeyboardHelper that listens on the MenuSelectionManager. + */ + private static KeyboardHelper keyboardHelper; + + /** + * Creates a new BasicPopupMenuUI object. + */ + public BasicPopupMenuUI() + { + popupMenuListener = new PopupMenuHandler(); + topWindowListener = new TopWindowListener(); + } + + /** + * Factory method to create a BasicPopupMenuUI for the given {@link + * JComponent}, which should be a {@link JMenuItem}. + * + * @param x The {@link JComponent} a UI is being created for. + * + * @return A BasicPopupMenuUI for the {@link JComponent}. + */ + public static ComponentUI createUI(JComponent x) + { + return new BasicPopupMenuUI(); + } + + /** + * Installs and initializes all fields for this UI delegate. Any properties + * of the UI that need to be initialized and/or set to defaults will be + * done now. It will also install any listeners necessary. + * + * @param c The {@link JComponent} that is having this UI installed. + */ + public void installUI(JComponent c) + { + super.installUI(c); + + // Install KeyboardHelper when the first popup is initialized. + if (numPopups == 0) + { + keyboardHelper = new KeyboardHelper(); + MenuSelectionManager msm = MenuSelectionManager.defaultManager(); + msm.addChangeListener(keyboardHelper); + } + numPopups++; + + popupMenu = (JPopupMenu) c; + popupMenu.setLayout(new DefaultMenuLayout(popupMenu, BoxLayout.Y_AXIS)); + popupMenu.setBorderPainted(true); + JPopupMenu.setDefaultLightWeightPopupEnabled(true); + + installDefaults(); + installListeners(); + installKeyboardActions(); + } + + /** + * This method installs the defaults that are defined in the Basic look + * and feel for this {@link JPopupMenu}. + */ + public void installDefaults() + { + LookAndFeel.installColorsAndFont(popupMenu, "PopupMenu.background", + "PopupMenu.foreground", "PopupMenu.font"); + LookAndFeel.installBorder(popupMenu, "PopupMenu.border"); + popupMenu.setOpaque(true); + } + + /** + * This method installs the listeners for the {@link JMenuItem}. + */ + protected void installListeners() + { + popupMenu.addPopupMenuListener(popupMenuListener); + } + + /** + * This method installs the keyboard actions for this {@link JPopupMenu}. + */ + protected void installKeyboardActions() + { + // We can't install the keyboard actions here, because then all + // popup menus would have their actions registered in the KeyboardManager. + // So we install it when the popup menu is opened, and uninstall it + // when it's closed. This is done in the KeyboardHelper class. + // Install InputMap. + } + + /** + * Called by the KeyboardHandler when a popup is made visible. + */ + void installKeyboardActionsImpl() + { + Object[] bindings; + if (popupMenu.getComponentOrientation().isLeftToRight()) + { + bindings = (Object[]) + SharedUIDefaults.get("PopupMenu.selectedWindowInputMapBindings"); + } + else + { + bindings = (Object[]) SharedUIDefaults.get + ("PopupMenu.selectedWindowInputMapBindings.RightToLeft"); + } + InputMap inputMap = LookAndFeel.makeComponentInputMap(popupMenu, bindings); + SwingUtilities.replaceUIInputMap(popupMenu, + JComponent.WHEN_IN_FOCUSED_WINDOW, + inputMap); + + // Install ActionMap. + SwingUtilities.replaceUIActionMap(popupMenu, getActionMap()); + } + + /** + * Creates and returns the shared action map for JTrees. + * + * @return the shared action map for JTrees + */ + private ActionMap getActionMap() + { + ActionMap am = (ActionMap) UIManager.get("PopupMenu.actionMap"); + if (am == null) + { + am = createDefaultActions(); + UIManager.getLookAndFeelDefaults().put("PopupMenu.actionMap", am); + } + return am; + } + + /** + * Creates the default actions when there are none specified by the L&F. + * + * @return the default actions + */ + private ActionMap createDefaultActions() + { + ActionMapUIResource am = new ActionMapUIResource(); + Action action = new NavigateAction("selectNext"); + am.put(action.getValue(Action.NAME), action); + action = new NavigateAction("selectPrevious"); + am.put(action.getValue(Action.NAME), action); + action = new NavigateAction("selectParent"); + am.put(action.getValue(Action.NAME), action); + action = new NavigateAction("selectChild"); + am.put(action.getValue(Action.NAME), action); + action = new NavigateAction("return"); + am.put(action.getValue(Action.NAME), action); + action = new NavigateAction("cancel"); + am.put(action.getValue(Action.NAME), action); + + return am; + } + + /** + * Performs the opposite of installUI. Any properties or resources that need + * to be cleaned up will be done now. It will also uninstall any listeners + * it has. In addition, any properties of this UI will be nulled. + * + * @param c The {@link JComponent} that is having this UI uninstalled. + */ + public void uninstallUI(JComponent c) + { + uninstallListeners(); + uninstallDefaults(); + uninstallKeyboardActions(); + popupMenu = null; + + // Install KeyboardHelper when the first popup is initialized. + numPopups--; + if (numPopups == 0) + { + MenuSelectionManager msm = MenuSelectionManager.defaultManager(); + msm.removeChangeListener(keyboardHelper); + } + + } + + /** + * This method uninstalls the defaults and sets any objects created during + * install to null + */ + protected void uninstallDefaults() + { + popupMenu.setBackground(null); + popupMenu.setBorder(null); + popupMenu.setFont(null); + popupMenu.setForeground(null); + } + + /** + * Unregisters all the listeners that this UI delegate was using. + */ + protected void uninstallListeners() + { + popupMenu.removePopupMenuListener(popupMenuListener); + } + + /** + * Uninstalls any keyboard actions. + */ + protected void uninstallKeyboardActions() + { + // We can't install the keyboard actions here, because then all + // popup menus would have their actions registered in the KeyboardManager. + // So we install it when the popup menu is opened, and uninstall it + // when it's closed. This is done in the KeyboardHelper class. + // Install InputMap. + } + + /** + * Called by the KeyboardHandler when a popup is made invisible. + */ + void uninstallKeyboardActionsImpl() + { + SwingUtilities.replaceUIInputMap(popupMenu, + JComponent.WHEN_IN_FOCUSED_WINDOW, null); + SwingUtilities.replaceUIActionMap(popupMenu, null); + } + + /** + * This method returns the minimum size of the JPopupMenu. + * + * @param c The JComponent to find a size for. + * + * @return The minimum size. + */ + public Dimension getMinimumSize(JComponent c) + { + return null; + } + + /** + * This method returns the preferred size of the JPopupMenu. + * + * @param c The JComponent to find a size for. + * + * @return The preferred size. + */ + public Dimension getPreferredSize(JComponent c) + { + return null; + } + + /** + * This method returns the minimum size of the JPopupMenu. + * + * @param c The JComponent to find a size for. + * + * @return The minimum size. + */ + public Dimension getMaximumSize(JComponent c) + { + return null; + } + + /** + * Return true if given mouse event is a platform popup trigger, and false + * otherwise + * + * @param e MouseEvent that is to be checked for popup trigger event + * + * @return true if given mouse event is a platform popup trigger, and false + * otherwise + */ + public boolean isPopupTrigger(MouseEvent e) + { + return false; + } + + /** + * This listener handles PopupMenuEvents fired by JPopupMenu + */ + private class PopupMenuHandler implements PopupMenuListener + { + /** + * This method is invoked when JPopupMenu is cancelled. + * + * @param event the PopupMenuEvent + */ + public void popupMenuCanceled(PopupMenuEvent event) + { + MenuSelectionManager manager = MenuSelectionManager.defaultManager(); + manager.clearSelectedPath(); + } + + /** + * This method is invoked when JPopupMenu becomes invisible + * + * @param event the PopupMenuEvent + */ + public void popupMenuWillBecomeInvisible(PopupMenuEvent event) + { + // remove listener that listens to component events fired + // by the top - level window that this popup belongs to. + Component invoker = popupMenu.getInvoker(); + Component rootContainer = SwingUtilities.getRoot(invoker); + if (rootContainer != null) + rootContainer.removeComponentListener(topWindowListener); + } + + /** + * This method is invoked when JPopupMenu becomes visible + * + * @param event the PopupMenuEvent + */ + public void popupMenuWillBecomeVisible(PopupMenuEvent event) + { + // Adds topWindowListener to top-level window to listener to + // ComponentEvents fired by it. We need to cancel this popup menu + // if topWindow to which this popup belongs was resized or moved. + Component invoker = popupMenu.getInvoker(); + Component rootContainer = SwingUtilities.getRoot(invoker); + if (rootContainer != null) + rootContainer.addComponentListener(topWindowListener); + + // if this popup menu is a free floating popup menu, + // then by default its first element should be always selected when + // this popup menu becomes visible. + MenuSelectionManager manager = MenuSelectionManager.defaultManager(); + + if (manager.getSelectedPath().length == 0) + { + // Set selected path to point to the first item in the popup menu + MenuElement[] path = new MenuElement[2]; + path[0] = popupMenu; + Component[] comps = popupMenu.getComponents(); + if (comps.length != 0 && comps[0] instanceof MenuElement) + { + path[1] = (MenuElement) comps[0]; + manager.setSelectedPath(path); + } + } + } + } + + /** + * ComponentListener that listens to Component Events fired by the top - + * level window to which popup menu belongs. If top-level window was + * resized, moved or hidded then popup menu will be hidded and selected + * path of current menu hierarchy will be set to null. + */ + private class TopWindowListener implements ComponentListener + { + /** + * This method is invoked when top-level window is resized. This method + * closes current menu hierarchy. + * + * @param e The ComponentEvent + */ + public void componentResized(ComponentEvent e) + { + MenuSelectionManager manager = MenuSelectionManager.defaultManager(); + manager.clearSelectedPath(); + } + + /** + * This method is invoked when top-level window is moved. This method + * closes current menu hierarchy. + * + * @param e The ComponentEvent + */ + public void componentMoved(ComponentEvent e) + { + MenuSelectionManager manager = MenuSelectionManager.defaultManager(); + manager.clearSelectedPath(); + } + + /** + * This method is invoked when top-level window is shown This method + * does nothing by default. + * + * @param e The ComponentEvent + */ + public void componentShown(ComponentEvent e) + { + MenuSelectionManager manager = MenuSelectionManager.defaultManager(); + manager.clearSelectedPath(); + } + + /** + * This method is invoked when top-level window is hidden This method + * closes current menu hierarchy. + * + * @param e The ComponentEvent + */ + public void componentHidden(ComponentEvent e) + { + MenuSelectionManager manager = MenuSelectionManager.defaultManager(); + manager.clearSelectedPath(); + } + } + +} diff --git a/libjava/classpath/javax/swing/plaf/basic/BasicProgressBarUI.java b/libjava/classpath/javax/swing/plaf/basic/BasicProgressBarUI.java new file mode 100644 index 000000000..bff6385ea --- /dev/null +++ b/libjava/classpath/javax/swing/plaf/basic/BasicProgressBarUI.java @@ -0,0 +1,962 @@ +/* BasicProgressBarUI.java -- + Copyright (C) 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 java.awt.Color; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.FontMetrics; +import java.awt.Graphics; +import java.awt.Insets; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.Shape; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.ComponentAdapter; +import java.awt.event.ComponentEvent; +import java.awt.event.ComponentListener; +import java.awt.geom.AffineTransform; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; + +import javax.swing.JComponent; +import javax.swing.JProgressBar; +import javax.swing.LookAndFeel; +import javax.swing.SwingConstants; +import javax.swing.SwingUtilities; +import javax.swing.Timer; +import javax.swing.UIManager; +import javax.swing.event.AncestorEvent; +import javax.swing.event.AncestorListener; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import javax.swing.plaf.ComponentUI; +import javax.swing.plaf.ProgressBarUI; + +/** + * The Basic Look and Feel UI delegate for the + * JProgressBar. + */ +public class BasicProgressBarUI extends ProgressBarUI +{ + /** + * A helper class that listens for ChangeEvents + * from the progressBar's model. + * + * @specnote Apparently this class was intended to be protected, + * but was made public by a compiler bug and is now + * public for compatibility. + */ + public class ChangeHandler implements ChangeListener + { + /** + * Called every time the state of the model changes. + * + * @param e The ChangeEvent given by the model. + */ + public void stateChanged(ChangeEvent e) + { + // Nothing to do but repaint. + progressBar.repaint(); + } + } + + /** + * This helper class is used to listen for + * PropertyChangeEvents from the progressBar. + */ + private class PropertyChangeHandler implements PropertyChangeListener + { + /** + * Called every time the properties of the + * progressBar change. + * + * @param e The PropertyChangeEvent given by the progressBar. + */ + public void propertyChange(PropertyChangeEvent e) + { + // Only need to listen for indeterminate changes. + // All other things are done on a repaint. + if (e.getPropertyName().equals("indeterminate")) + if (((Boolean) e.getNewValue()).booleanValue() + && progressBar.isShowing()) + startAnimationTimer(); + else + stopAnimationTimer(); + } + } + + /** + * Receives notification when the progressbar is becoming visible or + * invisible and starts/stops the animation timer accordingly. + * + * @author Roman Kennke (kennke@aicas.com) + */ + private class AncestorHandler implements AncestorListener + { + + /** + * Receives notification when the progressbar is becoming visible. This + * starts the animation timer if the progressbar is indeterminate. + * + * @param event the ancestor event + */ + public void ancestorAdded(AncestorEvent event) + { + if (progressBar.isIndeterminate()) + startAnimationTimer(); + } + + /** + * Receives notification when the progressbar is becoming invisible. This + * stops the animation timer if the progressbar is indeterminate. + * + * @param event the ancestor event + */ + public void ancestorRemoved(AncestorEvent event) + { + stopAnimationTimer(); + } + + /** + * Receives notification when an ancestor has been moved. We don't need to + * do anything here. + */ + public void ancestorMoved(AncestorEvent event) + { + // Nothing to do here. + } + + } + + /** + * This helper class is used to listen for + * the animationTimer's intervals. On every interval, + * the bouncing box should move. + */ + private class Animator implements ActionListener + { + /** + * Called every time the animationTimer reaches + * its interval. + * + * @param e The ActionEvent given by the timer. + */ + public void actionPerformed(ActionEvent e) + { + // Incrementing the animation index will cause + // a repaint. + incrementAnimationIndex(); + } + } + + /** + * Receives notification when the size of the progress bar changes and + * invalidates the layout information for the box calculation in + * {@link BasicProgressBarUI#getBox(Rectangle)}. + * + * @author Roman Kennke (kennke@aicas.com) + */ + private class ComponentHandler extends ComponentAdapter + { + /** + * Receives notification when the size of the progress bar changes and + * invalidates the layout information for the box calculation in + * {@link BasicProgressBarUI#getBox}. + * + * @param e the component event + */ + public void componentResized(ComponentEvent e) + { + boxDependent = -1; + boxIndependent = -1; + incr = -1; + } + } + + /** + * Holds the value of the bouncing box that is returned by {@link #getBox}. + * + * @since 1.5 + */ + protected Rectangle boxRect; + + /** The timer used to move the bouncing box. */ + private transient Timer animationTimer; + + // The total number of frames must be an even number. + // The total number of frames is calculated from + // the cycleTime and repaintInterval given by + // the basic Look and Feel defaults. + // + // +-----------------------------------------------+ + // | frame0 | frame1 | frame2 | frame 3 | frame 4 | + // | | frame7 | frame6 | frame 5 | | + // +-----------------------------------------------+ + + /** The current animation index. */ + private transient int animationIndex; + + /** The total number of frames.*/ + private transient int numFrames; + + /** The helper that moves the bouncing box. */ + private transient Animator animation; + + /** The helper that listens for property change events. */ + private transient PropertyChangeHandler propertyListener; + + /** The Listener for the model. */ + protected ChangeListener changeListener; + + /** The progressBar for this UI. */ + protected JProgressBar progressBar; + + + /** + * The size of the box returned by {@link #getBox} in the orientation + * direction of the progress bar. This is package private to avoid accessor + * method. + */ + transient double boxDependent = - 1; + + /** + * The size of the box returned by {@link #getBox} against the orientation + * direction of the progress bar. This is package private to avoid accessor + * method. + */ + transient int boxIndependent = - 1; + + /** + * The increment for box animation. This is package private to avoid accessor + * method. + */ + transient double incr = -1; + + /** The length of the cell. The cell is the painted part. */ + private transient int cellLength; + + /** The gap between cells. */ + private transient int cellSpacing; + + /** The color of the text when the bar is not over it.*/ + private transient Color selectionBackground; + + /** The color of the text when the bar is over it. */ + private transient Color selectionForeground; + + /** + * Listens for notification when the component becomes showing and + * starts/stops the animation timer. + */ + private AncestorListener ancestorListener; + + /** + * Listens for resize events on the progress bar and invalidates some + * layout info. + */ + private ComponentListener componentListener; + + /** + * Creates a new BasicProgressBarUI object. + */ + public BasicProgressBarUI() + { + super(); + } + + /** + * Creates a new BasicProgressBarUI for the component. + * + * @param x The JComponent to create the UI for. + * + * @return A new BasicProgressBarUI. + */ + public static ComponentUI createUI(JComponent x) + { + return new BasicProgressBarUI(); + } + + /** + * This method returns the length of the bar (from the minimum) + * in pixels (or units that the Graphics object draws in) based + * on the progressBar's getPercentComplete() value. + * + * @param b The insets of the progressBar. + * @param width The width of the progressBar. + * @param height The height of the progressBar. + * + * @return The length of the bar that should be painted in pixels. + */ + protected int getAmountFull(Insets b, int width, int height) + { + double percentDone = progressBar.getPercentComplete(); + if (progressBar.getOrientation() == JProgressBar.HORIZONTAL) + return (int) (percentDone * (width - b.left - b.right)); + else + return (int) (percentDone * (height - b.top - b.bottom)); + } + + /** + * The current animation index. + * + * @return The current animation index. + */ + protected int getAnimationIndex() + { + return animationIndex; + } + + /** + * This method returns the size and position of the bouncing box + * for the current animation index. It stores the values in the + * given rectangle and returns it. It returns null if no box should + * be drawn. + * + * @param r The bouncing box rectangle. + * + * @return The bouncing box rectangle. + */ + protected Rectangle getBox(Rectangle r) + { + if (!progressBar.isIndeterminate()) + return null; + if (r == null) + r = new Rectangle(); + + Rectangle vr = new Rectangle(); + SwingUtilities.calculateInnerArea(progressBar, vr); + + // Recalculate the metrics only when size of the progressbar has changed. + if (incr == -1 || boxDependent == -1 || boxIndependent == -1) + { + //numFrames has to be an even number as defined by spec. + int iterations = numFrames / 2; + if (progressBar.getOrientation() == JProgressBar.HORIZONTAL) + { + boxDependent = vr.width / 6.; + incr = ((double) (vr.width - boxDependent)) / (double) iterations; + boxIndependent = vr.height; + } + else + { + boxDependent = vr.height / 6.; + incr = ((double) (vr.height - boxDependent)) / (double) iterations; + boxIndependent = vr.width; + } + } + + int index = getAnimationIndex(); + if (animationIndex > numFrames / 2) + index = numFrames - getAnimationIndex(); + + if (progressBar.getOrientation() == JProgressBar.HORIZONTAL) + { + r.x = vr.x + (int) (incr * index); + r.y = vr.y; + r.width = (int) boxDependent; + r.height = (int) boxIndependent; + } + else + { + r.x = vr.x; + r.y = vr.height - (int) (incr * index) + vr.y - (int) boxDependent; + r.width = (int) boxIndependent; + r.height = (int) boxDependent; + } + return r; + } + + /** + * This method returns the length of the cells. + * + * @return The cell length. + */ + protected int getCellLength() + { + return cellLength; + } + + /** + * This method returns the spacing between cells. + * + * @return The cell gap. + */ + protected int getCellSpacing() + { + return cellSpacing; + } + + /** + * This method returns the maximum size of the JComponent. + * If it returns null, it is up to the LayoutManager + * to give it a size. + * + * @param c The component to find a maximum size for. + * + * @return The maximum size. + */ + public Dimension getMaximumSize(JComponent c) + { + Insets insets = c.getInsets(); + Dimension ret; + int orientation = progressBar.getOrientation(); + if (orientation == JProgressBar.VERTICAL) + { + ret = getPreferredInnerVertical(); + ret.height = Short.MAX_VALUE; + ret.width += insets.left + insets.right; + } + else + { + ret = getPreferredInnerHorizontal(); + ret.width = Short.MAX_VALUE; + ret.height += insets.top + insets.bottom; + } + return ret; + } + + /** + * This method returns the minimum size of the JComponent. + * If it returns null, it is up to the LayoutManager to + * give it a size. + * + * @param c The component to find a minimum size for. + * + * @return The minimum size. + */ + public Dimension getMinimumSize(JComponent c) + { + Insets insets = c.getInsets(); + Dimension ret; + int orientation = progressBar.getOrientation(); + if (orientation == JProgressBar.VERTICAL) + { + ret = getPreferredInnerVertical(); + ret.height = 10; + ret.width += insets.left + insets.right; + } + else + { + ret = getPreferredInnerHorizontal(); + ret.width = 10; + ret.height += insets.top + insets.bottom; + } + return ret; + } + + /** + * This method returns the preferred size of the inner + * rectangle (the bounds without the insets) if the + * progressBar is horizontal. + * + * @return The preferred size of the progressBar minus + * insets if it's horizontal. + */ + protected Dimension getPreferredInnerHorizontal() + { + Font font = progressBar.getFont(); + FontMetrics fm = progressBar.getFontMetrics(font); + + int stringWidth = 0; + String str = progressBar.getString(); + if (str != null) + stringWidth = fm.stringWidth(progressBar.getString()); + Insets i = progressBar.getInsets(); + int prefWidth = Math.max(200 - i.left - i.right, stringWidth); + + int stringHeight = 0; + if (str != null) + stringHeight = fm.getHeight(); + int prefHeight = Math.max(16 - i.top - i.bottom, stringHeight); + + return new Dimension(prefWidth, prefHeight); + } + + /** + * This method returns the preferred size of the inner + * rectangle (the bounds without insets) if the + * progressBar is vertical. + * + * @return The preferred size of the progressBar minus + * insets if it's vertical. + */ + protected Dimension getPreferredInnerVertical() + { + Font font = progressBar.getFont(); + FontMetrics fm = progressBar.getFontMetrics(font); + + int stringWidth = 0; + String str = progressBar.getString(); + if (str != null) + stringWidth = fm.stringWidth(progressBar.getString()); + Insets i = progressBar.getInsets(); + int prefHeight = Math.max(200 - i.left - i.right, stringWidth); + + int stringHeight = 0; + if (str != null) + stringHeight = fm.getHeight(); + int prefWidth = Math.max(16 - i.top - i.bottom, stringHeight); + + return new Dimension(prefWidth, prefHeight); + } + + /** + * This method returns the preferred size of the + * given JComponent. If it returns null, then it + * is up to the LayoutManager to give it a size. + * + * @param c The component to find the preferred size for. + * + * @return The preferred size of the component. + */ + public Dimension getPreferredSize(JComponent c) + { + Insets insets = c.getInsets(); + Dimension ret; + int orientation = progressBar.getOrientation(); + if (orientation == JProgressBar.VERTICAL) + ret = getPreferredInnerVertical(); + else + ret = getPreferredInnerHorizontal(); + ret.width += insets.left + insets.right; + ret.height += insets.top + insets.bottom; + return ret; + } + + /** + * This method returns the Color that the text is shown in when the bar is + * not over the text. + * + * @return The color of the text when the bar is not over it. + */ + protected Color getSelectionBackground() + { + return selectionBackground; + } + + /** + * This method returns the Color that the text is shown in when the bar is + * over the text. + * + * @return The color of the text when the bar is over it. + */ + protected Color getSelectionForeground() + { + return selectionForeground; + } + + /** + * This method returns the point (the top left of the bounding box) + * where the text should be painted. + * + * @param g The Graphics object to measure FontMetrics with. + * @param progressString The string to paint. + * @param x The x coordinate of the overall bounds box. + * @param y The y coordinate of the overall bounds box. + * @param width The width of the overall bounds box. + * @param height The height of the overall bounds box. + * + * @return The top left of the bounding box where text should be painted. + */ + protected Point getStringPlacement(Graphics g, String progressString, int x, + int y, int width, int height) + { + Rectangle tr = new Rectangle(); + Rectangle vr = new Rectangle(); + Rectangle ir = new Rectangle(); + + if (progressBar.getOrientation() == JProgressBar.HORIZONTAL) + vr.setBounds(x, y, width, height); + else + vr.setBounds(y, x, height, width); + + Font f = g.getFont(); + FontMetrics fm = g.getFontMetrics(f); + + SwingUtilities.layoutCompoundLabel(progressBar, fm, progressString, null, + SwingConstants.CENTER, + SwingConstants.CENTER, + SwingConstants.CENTER, + SwingConstants.CENTER, vr, ir, tr, 0); + + if (progressBar.getOrientation() == JProgressBar.HORIZONTAL) + return new Point(tr.x, tr.y); + else + return new Point(tr.y, tr.x); + } + + /** + * This method increments the animation index. + */ + protected void incrementAnimationIndex() + { + animationIndex++; + //numFrames is like string length, it should be named numFrames or something + if (animationIndex >= numFrames) + animationIndex = 0; + progressBar.repaint(); + } + + /** + * This method paints the progressBar. It delegates its responsibilities + * to paintDeterminate and paintIndeterminate. + * + * @param g The Graphics object to paint with. + * @param c The JComponent to paint. + */ + public void paint(Graphics g, JComponent c) + { + if (! progressBar.isIndeterminate()) + paintDeterminate(g, c); + else + paintIndeterminate(g, c); + } + + /** + * This method is called if the painting to be done is + * for a determinate progressBar. + * + * @param g The Graphics object to paint with. + * @param c The JComponent to paint. + */ + protected void paintDeterminate(Graphics g, JComponent c) + { + Color saved = g.getColor(); + int space = getCellSpacing(); + int len = getCellLength(); + int max = progressBar.getMaximum(); + int min = progressBar.getMinimum(); + int value = progressBar.getValue(); + + Rectangle vr = SwingUtilities.calculateInnerArea(c, new Rectangle()); + Rectangle or = progressBar.getBounds(); + Insets insets = c.getInsets(); + + int amountFull = getAmountFull(insets, or.width, or.height); + + if (progressBar.getOrientation() == JProgressBar.HORIZONTAL) + { + g.setColor(c.getForeground()); + g.fillRect(vr.x, vr.y, amountFull, vr.height); + } + else + { + g.setColor(c.getForeground()); + g.fillRect(vr.x, vr.y + vr.height - amountFull, vr.width, + amountFull); + } + + if (progressBar.isStringPainted() && !progressBar.getString().equals("")) + paintString(g, 0, 0, or.width, or.height, amountFull, insets); + g.setColor(saved); + } + + /** + * This method is called if the painting to be done is for + * an indeterminate progressBar. + * + * @param g The Graphics object to paint with. + * @param c The JComponent to paint. + */ + protected void paintIndeterminate(Graphics g, JComponent c) + { + //need to paint the box at it's current position. no text is painted since + //all we're doing is bouncing back and forth + Color saved = g.getColor(); + Insets insets = c.getInsets(); + + Rectangle or = c.getBounds(); + Rectangle vr = new Rectangle(); + SwingUtilities.calculateInnerArea(c, vr); + + g.setColor(c.getBackground()); + g.fillRect(vr.x, vr.y, vr.width, vr.height); + + boxRect = getBox(boxRect); + + g.setColor(c.getForeground()); + g.fillRect(boxRect.x, boxRect.y, boxRect.width, boxRect.height); + + if (progressBar.isStringPainted() && !progressBar.getString().equals("")) + paintString(g, 0, 0, or.width, or.height, + getAmountFull(insets, or.width, or.height), insets); + + g.setColor(saved); + } + + /** + * This method paints the string for the progressBar. + * + * @param g The Graphics object to paint with. + * @param x The x coordinate of the progressBar. + * @param y The y coordinate of the progressBar. + * @param width The width of the progressBar. + * @param height The height of the progressBar. + * @param amountFull The amount of the progressBar that has its bar filled. + * @param b The insets of the progressBar. + */ + protected void paintString(Graphics g, int x, int y, int width, int height, + int amountFull, Insets b) + { + String str = progressBar.getString(); + int full = getAmountFull(b, width, height); + Point placement = getStringPlacement(g, progressBar.getString(), + x + b.left, y + b.top, + width - b.left - b.right, + height - b.top - b.bottom); + Color savedColor = g.getColor(); + Shape savedClip = g.getClip(); + FontMetrics fm = g.getFontMetrics(progressBar.getFont()); + + if (progressBar.getOrientation() == JProgressBar.VERTICAL) + { + AffineTransform rotate = AffineTransform.getRotateInstance(Math.PI / 2); + g.setFont(progressBar.getFont().deriveFont(rotate)); + placement.x = width - placement.x - fm.getAscent(); + } + else + { + placement.y += fm.getAscent(); + } + + g.setColor(getSelectionForeground()); + g.setClip(0, 0, full + b.left, height); + g.drawString(str, placement.x, placement.y); + g.setColor(getSelectionBackground()); + g.setClip(full + b.left, 0, width - full, height); + g.drawString(str, placement.x, placement.y); + g.setClip(savedClip); + g.setColor(savedColor); + } + + /** + * This method sets the current animation index. If the index is greater than + * the number of frames, it resets to 0. + * + * @param newValue The new animation index. + */ + protected void setAnimationIndex(int newValue) + { + animationIndex = (newValue <= numFrames) ? newValue : 0; + progressBar.repaint(); + } + + /** + * This method sets the cell length. + * + * @param cellLen The cell length. + */ + protected void setCellLength(int cellLen) + { + cellLength = cellLen; + } + + /** + * This method sets the cell spacing. + * + * @param cellSpace The cell spacing. + */ + protected void setCellSpacing(int cellSpace) + { + cellSpacing = cellSpace; + } + + /** + * This method starts the animation timer. It is called + * when the propertyChangeListener detects that the progressBar + * has changed to indeterminate mode. + * + * @since 1.4 + */ + protected void startAnimationTimer() + { + if (animationTimer != null) + animationTimer.start(); + } + + /** + * This method stops the animation timer. It is called when + * the propertyChangeListener detects that the progressBar + * has changed to determinate mode. + * + * @since 1.4 + */ + protected void stopAnimationTimer() + { + if (animationTimer != null) + animationTimer.stop(); + setAnimationIndex(0); + } + + /** + * This method changes the settings for the progressBar to + * the defaults provided by the current Look and Feel. + */ + protected void installDefaults() + { + LookAndFeel.installColorsAndFont(progressBar, "ProgressBar.background", + "ProgressBar.foreground", + "ProgressBar.font"); + LookAndFeel.installBorder(progressBar, "ProgressBar.border"); + progressBar.setOpaque(true); + + selectionForeground = UIManager.getColor("ProgressBar.selectionForeground"); + selectionBackground = UIManager.getColor("ProgressBar.selectionBackground"); + cellLength = UIManager.getInt("ProgressBar.cellLength"); + cellSpacing = UIManager.getInt("ProgressBar.cellSpacing"); + + int repaintInterval = UIManager.getInt("ProgressBar.repaintInterval"); + int cycleTime = UIManager.getInt("ProgressBar.cycleTime"); + + if (cycleTime % repaintInterval != 0 + && (cycleTime / repaintInterval) % 2 != 0) + { + int div = (cycleTime / repaintInterval) + 2; + div /= 2; + div *= 2; + cycleTime = div * repaintInterval; + } + setAnimationIndex(0); + numFrames = cycleTime / repaintInterval; + animationTimer.setDelay(repaintInterval); + } + + /** + * The method uninstalls any defaults that were + * set by the current Look and Feel. + */ + protected void uninstallDefaults() + { + progressBar.setFont(null); + progressBar.setForeground(null); + progressBar.setBackground(null); + + selectionForeground = null; + selectionBackground = null; + } + + /** + * This method registers listeners to all the + * components that this UI delegate needs to listen to. + */ + protected void installListeners() + { + changeListener = new ChangeHandler(); + propertyListener = new PropertyChangeHandler(); + animation = new Animator(); + + progressBar.addChangeListener(changeListener); + progressBar.addPropertyChangeListener(propertyListener); + animationTimer.addActionListener(animation); + + ancestorListener = new AncestorHandler(); + progressBar.addAncestorListener(ancestorListener); + + componentListener = new ComponentHandler(); + progressBar.addComponentListener(componentListener); + } + + /** + * This method unregisters listeners to all the + * components that were listened to. + */ + protected void uninstallListeners() + { + progressBar.removeChangeListener(changeListener); + progressBar.removePropertyChangeListener(propertyListener); + animationTimer.removeActionListener(animation); + + changeListener = null; + propertyListener = null; + animation = null; + + if (ancestorListener != null) + progressBar.removeAncestorListener(ancestorListener); + ancestorListener = null; + + if (componentListener != null) + progressBar.removeComponentListener(componentListener); + componentListener = null; + } + + /** + * This method installs the UI for the given JComponent. + * This includes setting up defaults and listeners as + * well as initializing any values or objects that + * the UI may need. + * + * @param c The JComponent that is having this UI installed. + */ + public void installUI(JComponent c) + { + super.installUI(c); + if (c instanceof JProgressBar) + { + progressBar = (JProgressBar) c; + + animationTimer = new Timer(200, null); + animationTimer.setRepeats(true); + + installDefaults(); + installListeners(); + } + if (progressBar.isIndeterminate()) + startAnimationTimer(); + } + + /** + * This method removes the UI for the given JComponent. + * This includes removing any listeners or defaults + * that the installUI may have set up. + * + * @param c The JComponent that is having this UI uninstalled. + */ + public void uninstallUI(JComponent c) + { + super.uninstallUI(c); + uninstallListeners(); + uninstallDefaults(); + + animationTimer = null; + progressBar = null; + } + +} diff --git a/libjava/classpath/javax/swing/plaf/basic/BasicRadioButtonMenuItemUI.java b/libjava/classpath/javax/swing/plaf/basic/BasicRadioButtonMenuItemUI.java new file mode 100644 index 000000000..f8f62e156 --- /dev/null +++ b/libjava/classpath/javax/swing/plaf/basic/BasicRadioButtonMenuItemUI.java @@ -0,0 +1,101 @@ +/* BasicRadioButtonMenuItemUI.java -- + Copyright (C) 2002, 2004 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 java.awt.event.MouseEvent; + +import javax.swing.JComponent; +import javax.swing.JMenuItem; +import javax.swing.MenuElement; +import javax.swing.MenuSelectionManager; +import javax.swing.UIDefaults; +import javax.swing.plaf.ComponentUI; + +/** + * UI Delegator for JRadioButtonMenuItem + */ +public class BasicRadioButtonMenuItemUI extends BasicMenuItemUI +{ + /** + * Creates a new BasicRadioButtonMenuItemUI object. + */ + public BasicRadioButtonMenuItemUI() + { + super(); + } + + /** + * Factory method to create a BasicRadioButtonMenuItemUI for the given {@link + * JComponent}, which should be a JRadioButtonMenuItem. + * + * @param b The {@link JComponent} a UI is being created for. + * + * @return A BasicRadioButtonMenuItemUI for the {@link JComponent}. + */ + public static ComponentUI createUI(JComponent b) + { + return new BasicRadioButtonMenuItemUI(); + } + + /** + * Returns the prefix for entries in the {@link UIDefaults} table. + * + * @return "RadioButtonMenuItem" + */ + protected String getPropertyPrefix() + { + return "RadioButtonMenuItem"; + } + + /** + * DOCUMENT ME! + * + * @param item DOCUMENT ME! + * @param e DOCUMENT ME! + * @param path DOCUMENT ME! + * @param manager DOCUMENT ME! + */ + public void processMouseEvent(JMenuItem item, MouseEvent e, + MenuElement[] path, + MenuSelectionManager manager) + { + // TODO: May not be implemented properly. + item.processMouseEvent(e, path, manager); + } +} diff --git a/libjava/classpath/javax/swing/plaf/basic/BasicRadioButtonUI.java b/libjava/classpath/javax/swing/plaf/basic/BasicRadioButtonUI.java new file mode 100644 index 000000000..ff374d1ab --- /dev/null +++ b/libjava/classpath/javax/swing/plaf/basic/BasicRadioButtonUI.java @@ -0,0 +1,301 @@ +/* BasicRadioButtonUI.java + Copyright (C) 2002, 2004, 2006, 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 java.awt.Color; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.Graphics; +import java.awt.Insets; +import java.awt.Rectangle; + +import javax.swing.AbstractButton; +import javax.swing.ButtonModel; +import javax.swing.Icon; +import javax.swing.JComponent; +import javax.swing.SwingUtilities; +import javax.swing.UIManager; +import javax.swing.plaf.ComponentUI; +import javax.swing.text.View; + +/** + * The BasicLookAndFeel UI implementation for + * {@link javax.swing.JRadioButton}s. + */ +public class BasicRadioButtonUI extends BasicToggleButtonUI +{ + /** + * The default icon for JRadioButtons. The default icon displays the usual + * RadioButton and is sensible to the selection state of the button, + * and can be used both as normal icon as well as selectedIcon. + */ + protected Icon icon; + + /** + * Creates and returns a new instance of <code>BasicRadioButtonUI</code>. + * + * @return a new instance of <code>BasicRadioButtonUI</code> + */ + public static ComponentUI createUI(final JComponent c) + { + return new BasicRadioButtonUI(); + } + + /** + * Creates a new instance of <code>BasicButtonUI</code>. + */ + public BasicRadioButtonUI() + { + // nothing to do + } + + /** + * Installs defaults from the Look & Feel table on the specified + * button. + * + * @param b the button on which to install the defaults + */ + protected void installDefaults(AbstractButton b) + { + super.installDefaults(b); + icon = UIManager.getIcon(getPropertyPrefix() + "icon"); + } + + /** + * Returns the prefix used for UIDefaults properties. This is + * <code>RadioButton</code> in this case. + * + * @return the prefix used for UIDefaults properties + */ + protected String getPropertyPrefix() + { + return "RadioButton."; + } + + /** + * Returns the default icon for JRadioButtons. + * The default icon displays the usual + * RadioButton and is sensible to the selection state of the button, + * and can be used both as normal icon as well as selectedIcon. + * + * @return the default icon for JRadioButtons + */ + public Icon getDefaultIcon() + { + return icon; + } + + /** + * Paints the RadioButton. + * + * @param g the Graphics context to paint with + * @param c the button to paint + */ + public void paint(Graphics g, JComponent c) + { + AbstractButton b = (AbstractButton) c; + Dimension size = c.getSize(); + Insets i = b.getInsets(); + textR.x = 0; + textR.y = 0; + textR.width = 0; + textR.height = 0; + iconR.x = 0; + iconR.y = 0; + iconR.width = 0; + iconR.height = 0; + viewR.x = i.left; + viewR.y = i.right; + viewR.width = size.width - i.left - i.right; + viewR.height = size.height - i.top - i.bottom; + + Font f = c.getFont(); + + g.setFont(f); + + // This is the icon that we use for layout. + Icon icon = b.getIcon(); + if (icon == null) + icon = getDefaultIcon(); + + // Figure out the correct icon. + Icon currentIcon = getCurrentIcon(b); + + // Do the layout. + String text = SwingUtilities.layoutCompoundLabel(c, g.getFontMetrics(f), + b.getText(), currentIcon == null ? getDefaultIcon() : currentIcon, + b.getVerticalAlignment(), b.getHorizontalAlignment(), + b.getVerticalTextPosition(), b.getHorizontalTextPosition(), + viewR, iconR, textR, b.getIconTextGap()); + + // .. and paint it. + if (currentIcon != null) + currentIcon.paintIcon(c, g, iconR.x, iconR.y); + + // Paint text and focus indicator. + if (text != null) + { + // Maybe render HTML in the radio button. + View v = (View) c.getClientProperty(BasicHTML.propertyKey); + if (v != null) + v.paint(g, textR); + else + paintText(g, b, textR, text); + + // Paint focus indicator if necessary. + if (b.hasFocus() && b.isFocusPainted() + && textR.width > 0 && textR.height > 0) + paintFocus(g, textR, size); + } + } + + /** + * Determines the icon to be displayed for the specified radio button. + * + * @param b the radio button + * + * @return the icon + */ + private Icon getCurrentIcon(AbstractButton b) + { + ButtonModel m = b.getModel(); + Icon currentIcon = b.getIcon(); + + if (currentIcon == null) + { + currentIcon = getDefaultIcon(); + } + else + { + if (! m.isEnabled()) + { + if (m.isSelected()) + currentIcon = b.getDisabledSelectedIcon(); + else + currentIcon = b.getDisabledIcon(); + } + else if (m.isPressed() && m.isArmed()) + { + currentIcon = b.getPressedIcon(); + if (currentIcon == null) + currentIcon = b.getSelectedIcon(); + } + else if (m.isSelected()) + { + if (b.isRolloverEnabled() && m.isRollover()) + { + currentIcon = b.getRolloverSelectedIcon(); + if (currentIcon == null) + currentIcon = b.getSelectedIcon(); + } + else + currentIcon = b.getSelectedIcon(); + } + else if (b.isRolloverEnabled() && m.isRollover()) + { + currentIcon = b.getRolloverIcon(); + } + if (currentIcon == null) + currentIcon = b.getIcon(); + } + return currentIcon; + } + + public Dimension getPreferredSize(JComponent c) + { + // This is basically the same code as in + // BasicGraphicsUtils.getPreferredButtonSize() but takes the default icon + // property into account. JRadioButton and subclasses always have an icon: + // the check box. If the user explicitly changes it with setIcon() that + // one will be used for layout calculations and painting instead. + // The other icon properties are ignored. + AbstractButton b = (AbstractButton) c; + + Insets insets = b.getInsets(); + + String text = b.getText(); + Icon i = b.getIcon(); + if (i == null) + i = getDefaultIcon(); + + textR.x = 0; + textR.y = 0; + textR.width = 0; + textR.height = 0; + iconR.x = 0; + iconR.y = 0; + iconR.width = 0; + iconR.height = 0; + viewR.x = 0; + viewR.y = 0; + viewR.width = Short.MAX_VALUE; + viewR.height = Short.MAX_VALUE; + + SwingUtilities.layoutCompoundLabel(b, // for the component orientation + b.getFontMetrics(b.getFont()), + text, i, b.getVerticalAlignment(), + b.getHorizontalAlignment(), + b.getVerticalTextPosition(), + b.getHorizontalTextPosition(), + viewR, iconR, textR, + text == null ? 0 : b.getIconTextGap()); + + Rectangle r = SwingUtilities.computeUnion(textR.x, textR.y, textR.width, + textR.height, iconR); + + return new Dimension(insets.left + r.width + insets.right, + insets.top + r.height + insets.bottom); + } + + /** + * Paints the focus indicator for JRadioButtons. + * + * @param g the graphics context + * @param tr the rectangle for the text label + * @param size the size of the <code>JRadioButton</code> component. + */ + protected void paintFocus(Graphics g, Rectangle tr, Dimension size) + { + Color focusColor = UIManager.getColor(getPropertyPrefix() + ".focus"); + Color saved = g.getColor(); + g.setColor(focusColor); + g.drawRect(tr.x, tr.y, tr.width, tr.height); + g.setColor(saved); + } +} diff --git a/libjava/classpath/javax/swing/plaf/basic/BasicRootPaneUI.java b/libjava/classpath/javax/swing/plaf/basic/BasicRootPaneUI.java new file mode 100644 index 000000000..26c7a532d --- /dev/null +++ b/libjava/classpath/javax/swing/plaf/basic/BasicRootPaneUI.java @@ -0,0 +1,292 @@ +/* BasicRootPaneUI.java -- + Copyright (C) 2002, 2004 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 java.awt.event.ActionEvent; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; + +import javax.swing.AbstractAction; +import javax.swing.ButtonModel; +import javax.swing.InputMap; +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JRootPane; +import javax.swing.LookAndFeel; +import javax.swing.SwingUtilities; +import javax.swing.UIManager; +import javax.swing.plaf.ActionMapUIResource; +import javax.swing.plaf.ComponentInputMapUIResource; +import javax.swing.plaf.ComponentUI; +import javax.swing.plaf.RootPaneUI; + +public class BasicRootPaneUI extends RootPaneUI + implements PropertyChangeListener +{ + + /** + * Performed when the user activates the default button inside the JRootPane, + * usually by pressing 'ENTER'. + */ + private class DefaultPressAction + extends AbstractAction + { + /** + * The JRootPane for which this action should be installed. + */ + private JRootPane rootPane; + + /** + * Creates a new DefaultPressAction for the specified JRootPane. + */ + DefaultPressAction(JRootPane rp) + { + rootPane = rp; + } + + /** + * Performes the action. + */ + public void actionPerformed(ActionEvent ev) + { + JButton b = rootPane.getDefaultButton(); + if (b != null) + { + ButtonModel m = b.getModel(); + m.setArmed(true); + m.setPressed(true); + } + } + } + + /** + * Performed when the user activates the default button inside the JRootPane, + * usually by releasing 'ENTER'. + */ + private class DefaultReleaseAction + extends AbstractAction + { + /** + * The JRootPane for which this action should be installed. + */ + private JRootPane rootPane; + + /** + * Creates a new DefaultReleaseAction for the specified JRootPane. + */ + DefaultReleaseAction(JRootPane rp) + { + rootPane = rp; + } + + /** + * Performes the action. + */ + public void actionPerformed(ActionEvent ev) + { + JButton b = rootPane.getDefaultButton(); + if (b != null) + { + ButtonModel m = b.getModel(); + m.setPressed(false); + m.setArmed(false); + } + } + } + + public static ComponentUI createUI(JComponent x) + { + return new BasicRootPaneUI(); + } + + public void installUI(JComponent c) + { + super.installUI(c); + if (c instanceof JRootPane) + { + JRootPane rp = (JRootPane) c; + installDefaults(rp); + installComponents(rp); + installListeners(rp); + installKeyboardActions(rp); + } + } + + /** + * Installs the look and feel defaults for JRootPane. + * + * @param rp the root pane to install the defaults to + */ + protected void installDefaults(JRootPane rp) + { + // TODO: What to do here, if anything? (might be a hook method) + } + + /** + * Installs additional look and feel components to the root pane. + * + * @param rp the root pane to install the components to + */ + protected void installComponents(JRootPane rp) + { + // All components are initialized in the JRootPane constructor, and since + // the createXXXPane methods are protected, I see no reasonable way, + // and no need to initialize them here. This method is here anyway + // for compatibility and to provide the necessary hooks to subclasses. + } + + /** + * Installs any look and feel specific listeners on the root pane. + * + * @param rp the root pane to install the listeners to + */ + protected void installListeners(JRootPane rp) + { + rp.addPropertyChangeListener(this); + } + + /** + * Installs look and feel keyboard actions on the root pane. + * + * @param rp the root pane to install the keyboard actions to + */ + protected void installKeyboardActions(JRootPane rp) + { + // Install the keyboard actions. + ActionMapUIResource am = new ActionMapUIResource(); + am.put("press", new DefaultPressAction(rp)); + am.put("release", new DefaultReleaseAction(rp)); + SwingUtilities.replaceUIActionMap(rp, am); + + // Install the input map from the UIManager. It seems like the actual + // bindings are installed in the JRootPane only when the defaultButton + // property receives a value. So we also only install an empty + // input map here, and fill it in propertyChange. + ComponentInputMapUIResource im = new ComponentInputMapUIResource(rp); + SwingUtilities.replaceUIInputMap(rp, JComponent.WHEN_IN_FOCUSED_WINDOW, + im); + } + + public void propertyChange(PropertyChangeEvent event) + { + JRootPane source = (JRootPane) event.getSource(); + String propertyName = event.getPropertyName(); + if (propertyName.equals("defaultButton")) + { + Object newValue = event.getNewValue(); + InputMap im = + SwingUtilities.getUIInputMap(source, + JComponent.WHEN_IN_FOCUSED_WINDOW); + if (newValue != null) + { + Object[] keybindings = (Object[]) UIManager.get( + "RootPane.defaultButtonWindowKeyBindings"); + LookAndFeel.loadKeyBindings(im, keybindings); + } + else + { + im.clear(); + } + } + } + + /** + * Uninstalls this UI from the root pane. This calls + * {@link #uninstallDefaults}, {@link #uninstallComponents}, + * {@link #uninstallListeners}, {@link #uninstallKeyboardActions} + * in this order. + * + * @param c the root pane to uninstall the UI from + */ + public void uninstallUI(JComponent c) + { + super.uninstallUI(c); + if (c instanceof JRootPane) + { + JRootPane rp = (JRootPane) c; + uninstallDefaults(rp); + uninstallComponents(rp); + uninstallListeners(rp); + uninstallKeyboardActions(rp); + } + } + + /** + * Uninstalls the look and feel defaults that have been installed in + * {@link #installDefaults}. + * + * @param rp the root pane to uninstall the defaults from + */ + protected void uninstallDefaults(JRootPane rp) + { + // We do nothing here. + } + + /** + * Uninstalls look and feel components from the root pane. + * + * @param rp the root pane to uninstall the components from + */ + protected void uninstallComponents(JRootPane rp) + { + // We do nothing here. + } + + /** + * Uninstalls any look and feel specific listeners from the root pane. + * + * @param rp the root pane to uninstall the listeners from + */ + protected void uninstallListeners(JRootPane rp) + { + rp.removePropertyChangeListener(this); + } + + /** + * Uninstalls look and feel keyboard actions from the root pane. + * + * @param rp the root pane to uninstall the keyboard actions from + */ + protected void uninstallKeyboardActions(JRootPane rp) + { + SwingUtilities.replaceUIActionMap(rp, null); + SwingUtilities.replaceUIInputMap(rp, JComponent.WHEN_IN_FOCUSED_WINDOW, + null); + } +} diff --git a/libjava/classpath/javax/swing/plaf/basic/BasicScrollBarUI.java b/libjava/classpath/javax/swing/plaf/basic/BasicScrollBarUI.java new file mode 100644 index 000000000..9f24f8aca --- /dev/null +++ b/libjava/classpath/javax/swing/plaf/basic/BasicScrollBarUI.java @@ -0,0 +1,1513 @@ +/* BasicScrollBarUI.java -- + Copyright (C) 2004, 2005, 2006, 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 java.awt.Color; +import java.awt.Component; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Insets; +import java.awt.LayoutManager; +import java.awt.Rectangle; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.event.MouseMotionListener; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; + +import javax.swing.AbstractAction; +import javax.swing.ActionMap; +import javax.swing.BoundedRangeModel; +import javax.swing.InputMap; +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JScrollBar; +import javax.swing.JSlider; +import javax.swing.LookAndFeel; +import javax.swing.SwingConstants; +import javax.swing.SwingUtilities; +import javax.swing.Timer; +import javax.swing.UIManager; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import javax.swing.plaf.ActionMapUIResource; +import javax.swing.plaf.ComponentUI; +import javax.swing.plaf.ScrollBarUI; + +/** + * The Basic Look and Feel UI delegate for JScrollBar. + */ +public class BasicScrollBarUI extends ScrollBarUI implements LayoutManager, + SwingConstants +{ + /** + * A helper class that listens to the two JButtons on each end of the + * JScrollBar. + */ + protected class ArrowButtonListener extends MouseAdapter + { + + /** + * Move the thumb in the direction specified by the button's arrow. If + * this button is held down, then it should keep moving the thumb. + * + * @param e The MouseEvent fired by the JButton. + */ + public void mousePressed(MouseEvent e) + { + scrollTimer.stop(); + scrollListener.setScrollByBlock(false); + if (e.getSource() == incrButton) + scrollListener.setDirection(POSITIVE_SCROLL); + else if (e.getSource() == decrButton) + scrollListener.setDirection(NEGATIVE_SCROLL); + scrollTimer.setDelay(100); + scrollTimer.start(); + } + + /** + * Stops the thumb when the JButton is released. + * + * @param e The MouseEvent fired by the JButton. + */ + public void mouseReleased(MouseEvent e) + { + scrollTimer.stop(); + scrollTimer.setDelay(300); + if (e.getSource() == incrButton) + scrollByUnit(POSITIVE_SCROLL); + else if (e.getSource() == decrButton) + scrollByUnit(NEGATIVE_SCROLL); + } + } + + /** + * A helper class that listens to the ScrollBar's model for ChangeEvents. + */ + protected class ModelListener implements ChangeListener + { + /** + * Called when the model changes. + * + * @param e The ChangeEvent fired by the model. + */ + public void stateChanged(ChangeEvent e) + { + calculatePreferredSize(); + updateThumbRect(); + scrollbar.repaint(); + } + } + + /** + * A helper class that listens to the ScrollBar's properties. + */ + public class PropertyChangeHandler implements PropertyChangeListener + { + /** + * Called when one of the ScrollBar's properties change. + * + * @param e The PropertyChangeEvent fired by the ScrollBar. + */ + public void propertyChange(PropertyChangeEvent e) + { + if (e.getPropertyName().equals("model")) + { + ((BoundedRangeModel) e.getOldValue()).removeChangeListener(modelListener); + scrollbar.getModel().addChangeListener(modelListener); + updateThumbRect(); + } + else if (e.getPropertyName().equals("orientation")) + { + uninstallListeners(); + uninstallComponents(); + uninstallDefaults(); + installDefaults(); + installComponents(); + installListeners(); + } + else if (e.getPropertyName().equals("enabled")) + { + Boolean b = (Boolean) e.getNewValue(); + if (incrButton != null) + incrButton.setEnabled(b.booleanValue()); + if (decrButton != null) + decrButton.setEnabled(b.booleanValue()); + } + } + } + + /** + * A helper class that listens for events from the timer that is used to + * move the thumb. + */ + protected class ScrollListener implements ActionListener + { + /** The direction the thumb moves in. */ + private transient int direction; + + /** Whether movement will be in blocks. */ + private transient boolean block; + + /** + * Creates a new ScrollListener object. The default is scrolling + * positively with block movement. + */ + public ScrollListener() + { + direction = POSITIVE_SCROLL; + block = true; + } + + /** + * Creates a new ScrollListener object using the given direction and + * block. + * + * @param dir The direction to move in. + * @param block Whether movement will be in blocks. + */ + public ScrollListener(int dir, boolean block) + { + direction = dir; + this.block = block; + } + + /** + * Sets the direction to scroll in. + * + * @param direction The direction to scroll in. + */ + public void setDirection(int direction) + { + this.direction = direction; + } + + /** + * Sets whether scrolling will be done in blocks. + * + * @param block Whether scrolling will be in blocks. + */ + public void setScrollByBlock(boolean block) + { + this.block = block; + } + + /** + * Called every time the timer reaches its interval. + * + * @param e The ActionEvent fired by the timer. + */ + public void actionPerformed(ActionEvent e) + { + if (block) + { + // Only need to check it if it's block scrolling + // We only block scroll if the click occurs + // in the track. + if (!trackListener.shouldScroll(direction)) + { + trackHighlight = NO_HIGHLIGHT; + scrollbar.repaint(); + return; + } + scrollByBlock(direction); + } + else + scrollByUnit(direction); + } + } + + /** + * Helper class that listens for movement on the track. + */ + protected class TrackListener extends MouseAdapter + implements MouseMotionListener + { + /** The current X coordinate of the mouse. */ + protected int currentMouseX; + + /** The current Y coordinate of the mouse. */ + protected int currentMouseY; + + /** + * The offset between the current mouse cursor and the current value of + * the scrollbar. + */ + protected int offset; + + /** + * This method is called when the mouse is being dragged. + * + * @param e The MouseEvent given. + */ + public void mouseDragged(MouseEvent e) + { + currentMouseX = e.getX(); + currentMouseY = e.getY(); + if (scrollbar.getValueIsAdjusting()) + { + int value; + if (scrollbar.getOrientation() == SwingConstants.HORIZONTAL) + value = valueForXPosition(currentMouseX) - offset; + else + value = valueForYPosition(currentMouseY) - offset; + + scrollbar.setValue(value); + } + } + + /** + * This method is called when the mouse is moved. + * + * @param e The MouseEvent given. + */ + public void mouseMoved(MouseEvent e) + { + if (thumbRect.contains(e.getPoint())) + thumbRollover = true; + else + thumbRollover = false; + } + + /** + * This method is called when the mouse is pressed. When it is pressed, + * the thumb should move in blocks towards the cursor. + * + * @param e The MouseEvent given. + */ + public void mousePressed(MouseEvent e) + { + currentMouseX = e.getX(); + currentMouseY = e.getY(); + + int value; + if (scrollbar.getOrientation() == SwingConstants.HORIZONTAL) + value = valueForXPosition(currentMouseX); + else + value = valueForYPosition(currentMouseY); + + if (! thumbRect.contains(e.getPoint())) + { + scrollTimer.stop(); + scrollListener.setScrollByBlock(true); + if (value > scrollbar.getValue()) + { + trackHighlight = INCREASE_HIGHLIGHT; + scrollListener.setDirection(POSITIVE_SCROLL); + } + else + { + trackHighlight = DECREASE_HIGHLIGHT; + scrollListener.setDirection(NEGATIVE_SCROLL); + } + scrollTimer.setDelay(100); + scrollTimer.start(); + } + else + { + // We'd like to keep track of where the cursor + // is inside the thumb. + // This works because the scrollbar's value represents + // "lower" edge of the thumb. The value at which + // the cursor is at must be greater or equal + // to that value. + + scrollListener.setScrollByBlock(false); + scrollbar.setValueIsAdjusting(true); + offset = value - scrollbar.getValue(); + } + scrollbar.repaint(); + } + + /** + * This method is called when the mouse is released. It should stop + * movement on the thumb + * + * @param e The MouseEvent given. + */ + public void mouseReleased(MouseEvent e) + { + scrollTimer.stop(); + scrollTimer.setDelay(300); + currentMouseX = e.getX(); + currentMouseY = e.getY(); + + if (shouldScroll(POSITIVE_SCROLL)) + scrollByBlock(POSITIVE_SCROLL); + else if (shouldScroll(NEGATIVE_SCROLL)) + scrollByBlock(NEGATIVE_SCROLL); + + trackHighlight = NO_HIGHLIGHT; + scrollListener.setScrollByBlock(false); + scrollbar.setValueIsAdjusting(true); + scrollbar.repaint(); + } + + /** + * A helper method that decides whether we should keep scrolling in the + * given direction. + * + * @param direction The direction to check for. + * + * @return Whether the thumb should keep scrolling. + */ + boolean shouldScroll(int direction) + { + int value; + if (scrollbar.getOrientation() == HORIZONTAL) + value = valueForXPosition(currentMouseX); + else + value = valueForYPosition(currentMouseY); + + if (thumbRect.contains(currentMouseX, currentMouseY)) + return false; + + if (direction == POSITIVE_SCROLL) + return value > scrollbar.getValue(); + else + return value < scrollbar.getValue(); + } + } + + /** The listener that listens to the JButtons. */ + protected ArrowButtonListener buttonListener; + + /** The listener that listens to the model. */ + protected ModelListener modelListener; + + /** The listener that listens to the scrollbar for property changes. */ + protected PropertyChangeListener propertyChangeListener; + + /** The listener that listens to the timer. */ + protected ScrollListener scrollListener; + + /** The listener that listens for MouseEvents on the track. */ + protected TrackListener trackListener; + + /** The JButton that decrements the scrollbar's value. */ + protected JButton decrButton; + + /** The JButton that increments the scrollbar's value. */ + protected JButton incrButton; + + /** The dimensions of the maximum thumb size. */ + protected Dimension maximumThumbSize; + + /** The dimensions of the minimum thumb size. */ + protected Dimension minimumThumbSize; + + /** The color of the thumb. */ + protected Color thumbColor; + + /** The outer shadow of the thumb. */ + protected Color thumbDarkShadowColor; + + /** The top and left edge color for the thumb. */ + protected Color thumbHighlightColor; + + /** The outer light shadow for the thumb. */ + protected Color thumbLightShadowColor; + + /** The color that is used when the mouse press occurs in the track. */ + protected Color trackHighlightColor; + + /** The color of the track. */ + protected Color trackColor; + + /** The size and position of the track. */ + protected Rectangle trackRect; + + /** The size and position of the thumb. */ + protected Rectangle thumbRect; + + /** Indicates that the decrease highlight should be painted. */ + protected static final int DECREASE_HIGHLIGHT = 1; + + /** Indicates that the increase highlight should be painted. */ + protected static final int INCREASE_HIGHLIGHT = 2; + + /** Indicates that no highlight should be painted. */ + protected static final int NO_HIGHLIGHT = 0; + + /** Indicates that the scrolling direction is positive. */ + private static final int POSITIVE_SCROLL = 1; + + /** Indicates that the scrolling direction is negative. */ + private static final int NEGATIVE_SCROLL = -1; + + /** The cached preferred size for the scrollbar. */ + private transient Dimension preferredSize; + + /** The current highlight status. */ + protected int trackHighlight; + + /** FIXME: Use this for something (presumably mouseDragged) */ + protected boolean isDragging; + + /** The timer used to move the thumb when the mouse is held. */ + protected Timer scrollTimer; + + /** The scrollbar this UI is acting for. */ + protected JScrollBar scrollbar; + + /** True if the mouse is over the thumb. */ + boolean thumbRollover; + + /** + * This method adds a component to the layout. + * + * @param name The name to associate with the component that is added. + * @param child The Component to add. + */ + public void addLayoutComponent(String name, Component child) + { + // You should not be adding stuff to this component. + // The contents are fixed. + } + + /** + * This method configures the scrollbar's colors. This can be done by + * looking up the standard colors from the Look and Feel defaults. + */ + protected void configureScrollBarColors() + { + trackColor = UIManager.getColor("ScrollBar.track"); + trackHighlightColor = UIManager.getColor("ScrollBar.trackHighlight"); + thumbColor = UIManager.getColor("ScrollBar.thumb"); + thumbHighlightColor = UIManager.getColor("ScrollBar.thumbHighlight"); + thumbDarkShadowColor = UIManager.getColor("ScrollBar.thumbDarkShadow"); + thumbLightShadowColor = UIManager.getColor("ScrollBar.thumbShadow"); + } + + /** + * This method creates an ArrowButtonListener. + * + * @return A new ArrowButtonListener. + */ + protected ArrowButtonListener createArrowButtonListener() + { + return new ArrowButtonListener(); + } + + /** + * This method creates a new JButton with the appropriate icon for the + * orientation. + * + * @param orientation The orientation this JButton uses. + * + * @return The increase JButton. + */ + protected JButton createIncreaseButton(int orientation) + { + return new BasicArrowButton(orientation); + } + + /** + * This method creates a new JButton with the appropriate icon for the + * orientation. + * + * @param orientation The orientation this JButton uses. + * + * @return The decrease JButton. + */ + protected JButton createDecreaseButton(int orientation) + { + return new BasicArrowButton(orientation); + } + + /** + * This method creates a new ModelListener. + * + * @return A new ModelListener. + */ + protected ModelListener createModelListener() + { + return new ModelListener(); + } + + /** + * This method creates a new PropertyChangeListener. + * + * @return A new PropertyChangeListener. + */ + protected PropertyChangeListener createPropertyChangeListener() + { + return new PropertyChangeHandler(); + } + + /** + * This method creates a new ScrollListener. + * + * @return A new ScrollListener. + */ + protected ScrollListener createScrollListener() + { + return new ScrollListener(); + } + + /** + * This method creates a new TrackListener. + * + * @return A new TrackListener. + */ + protected TrackListener createTrackListener() + { + return new TrackListener(); + } + + /** + * This method returns a new BasicScrollBarUI. + * + * @param c The JComponent to create a UI for. + * + * @return A new BasicScrollBarUI. + */ + public static ComponentUI createUI(JComponent c) + { + return new BasicScrollBarUI(); + } + + /** + * This method returns the maximum size for this JComponent. + * + * @param c The JComponent to measure the maximum size for. + * + * @return The maximum size for the component. + */ + public Dimension getMaximumSize(JComponent c) + { + return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE); + } + + /** + * This method returns the maximum thumb size. + * + * @return The maximum thumb size. + */ + protected Dimension getMaximumThumbSize() + { + return maximumThumbSize; + } + + /** + * This method returns the minimum size for this JComponent. + * + * @param c The JComponent to measure the minimum size for. + * + * @return The minimum size for the component. + */ + public Dimension getMinimumSize(JComponent c) + { + return getPreferredSize(c); + } + + /** + * This method returns the minimum thumb size. + * + * @return The minimum thumb size. + */ + protected Dimension getMinimumThumbSize() + { + return minimumThumbSize; + } + + /** + * This method calculates the preferred size since calling + * getPreferredSize() returns a cached value. + * This is package-private to avoid an accessor method. + */ + void calculatePreferredSize() + { + int height; + int width; + height = width = 0; + + if (scrollbar.getOrientation() == SwingConstants.HORIZONTAL) + { + width += incrButton.getPreferredSize().getWidth(); + width += decrButton.getPreferredSize().getWidth(); + width += 16; + height = UIManager.getInt("ScrollBar.width"); + } + else + { + height += incrButton.getPreferredSize().getHeight(); + height += decrButton.getPreferredSize().getHeight(); + height += 16; + width = UIManager.getInt("ScrollBar.width"); + } + + Insets insets = scrollbar.getInsets(); + + height += insets.top + insets.bottom; + width += insets.left + insets.right; + + preferredSize = new Dimension(width, height); + } + + /** + * This method returns a cached value of the preferredSize. The only + * restrictions are: If the scrollbar is horizontal, the height should be + * the maximum of the height of the JButtons and the minimum width of the + * thumb. For vertical scrollbars, the calculation is similar (swap width + * for height and vice versa). + * + * @param c The JComponent to measure. + * + * @return The preferredSize. + */ + public Dimension getPreferredSize(JComponent c) + { + calculatePreferredSize(); + return preferredSize; + } + + /** + * This method returns the thumb's bounds based on the current value of the + * scrollbar. This method updates the cached value and returns that. + * + * @return The thumb bounds. + */ + protected Rectangle getThumbBounds() + { + return thumbRect; + } + + /** + * This method calculates the bounds of the track. This method updates the + * cached value and returns it. + * + * @return The track's bounds. + */ + protected Rectangle getTrackBounds() + { + return trackRect; + } + + /** + * This method installs any addition Components that are a part of or + * related to this scrollbar. + */ + protected void installComponents() + { + int orientation = scrollbar.getOrientation(); + switch (orientation) + { + case JScrollBar.HORIZONTAL: + incrButton = createIncreaseButton(EAST); + decrButton = createDecreaseButton(WEST); + break; + default: + incrButton = createIncreaseButton(SOUTH); + decrButton = createDecreaseButton(NORTH); + break; + } + + if (incrButton != null) + scrollbar.add(incrButton); + if (decrButton != null) + scrollbar.add(decrButton); + } + + /** + * This method installs the defaults for the scrollbar specified by the + * Basic Look and Feel. + */ + protected void installDefaults() + { + LookAndFeel.installColors(scrollbar, "ScrollBar.background", + "ScrollBar.foreground"); + LookAndFeel.installBorder(scrollbar, "ScrollBar.border"); + scrollbar.setOpaque(true); + scrollbar.setLayout(this); + + configureScrollBarColors(); + + maximumThumbSize = UIManager.getDimension("ScrollBar.maximumThumbSize"); + minimumThumbSize = UIManager.getDimension("ScrollBar.minimumThumbSize"); + } + + /** + * Installs the input map from the look and feel defaults, and a + * corresponding action map. Note the the keyboard bindings will only + * work when the {@link JScrollBar} component has the focus, which is rare. + */ + protected void installKeyboardActions() + { + InputMap keyMap = getInputMap( + JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); + SwingUtilities.replaceUIInputMap(scrollbar, + JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, keyMap); + ActionMap map = getActionMap(); + SwingUtilities.replaceUIActionMap(scrollbar, map); + } + + /** + * Uninstalls the input map and action map installed by + * {@link #installKeyboardActions()}. + */ + protected void uninstallKeyboardActions() + { + SwingUtilities.replaceUIActionMap(scrollbar, null); + SwingUtilities.replaceUIInputMap(scrollbar, + JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, null); + } + + InputMap getInputMap(int condition) + { + if (condition == JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT) + return (InputMap) UIManager.get("ScrollBar.focusInputMap"); + return null; + } + + /** + * Returns the action map for the {@link JScrollBar}. All scroll bars + * share a single action map which is created the first time this method is + * called, then stored in the UIDefaults table for subsequent access. + * + * @return The shared action map. + */ + ActionMap getActionMap() + { + ActionMap map = (ActionMap) UIManager.get("ScrollBar.actionMap"); + + if (map == null) // first time here + { + map = createActionMap(); + if (map != null) + UIManager.put("ScrollBar.actionMap", map); + } + return map; + } + + /** + * Creates the action map shared by all {@link JSlider} instances. + * This method is called once by {@link #getActionMap()} when it + * finds no action map in the UIDefaults table...after the map is + * created, it gets added to the defaults table so that subsequent + * calls to {@link #getActionMap()} will return the same shared + * instance. + * + * @return The action map. + */ + ActionMap createActionMap() + { + ActionMap map = new ActionMapUIResource(); + map.put("positiveUnitIncrement", + new AbstractAction("positiveUnitIncrement") { + public void actionPerformed(ActionEvent event) + { + JScrollBar sb = (JScrollBar) event.getSource(); + if (sb.isVisible()) + { + int delta = sb.getUnitIncrement(1); + sb.setValue(sb.getValue() + delta); + } + } + } + ); + map.put("positiveBlockIncrement", + new AbstractAction("positiveBlockIncrement") { + public void actionPerformed(ActionEvent event) + { + JScrollBar sb = (JScrollBar) event.getSource(); + if (sb.isVisible()) + { + int delta = sb.getBlockIncrement(1); + sb.setValue(sb.getValue() + delta); + } + } + } + ); + map.put("negativeUnitIncrement", + new AbstractAction("negativeUnitIncrement") { + public void actionPerformed(ActionEvent event) + { + JScrollBar sb = (JScrollBar) event.getSource(); + if (sb.isVisible()) + { + int delta = sb.getUnitIncrement(-1); + sb.setValue(sb.getValue() + delta); + } + } + } + ); + map.put("negativeBlockIncrement", + new AbstractAction("negativeBlockIncrement") { + public void actionPerformed(ActionEvent event) + { + JScrollBar sb = (JScrollBar) event.getSource(); + if (sb.isVisible()) + { + int delta = sb.getBlockIncrement(-1); + sb.setValue(sb.getValue() + delta); + } + } + } + ); + map.put("minScroll", + new AbstractAction("minScroll") { + public void actionPerformed(ActionEvent event) + { + JScrollBar sb = (JScrollBar) event.getSource(); + if (sb.isVisible()) + { + sb.setValue(sb.getMinimum()); + } + } + } + ); + map.put("maxScroll", + new AbstractAction("maxScroll") { + public void actionPerformed(ActionEvent event) + { + JScrollBar sb = (JScrollBar) event.getSource(); + if (sb.isVisible()) + { + sb.setValue(sb.getMaximum()); + } + } + } + ); + return map; + } + + /** + * This method installs any listeners for the scrollbar. This method also + * installs listeners for things such as the JButtons and the timer. + */ + protected void installListeners() + { + scrollListener = createScrollListener(); + trackListener = createTrackListener(); + buttonListener = createArrowButtonListener(); + modelListener = createModelListener(); + propertyChangeListener = createPropertyChangeListener(); + + scrollbar.addMouseMotionListener(trackListener); + scrollbar.addMouseListener(trackListener); + + incrButton.addMouseListener(buttonListener); + decrButton.addMouseListener(buttonListener); + + scrollbar.addPropertyChangeListener(propertyChangeListener); + scrollbar.getModel().addChangeListener(modelListener); + + scrollTimer.addActionListener(scrollListener); + } + + /** + * This method installs the UI for the component. This can include setting + * up listeners, defaults, and components. This also includes initializing + * any data objects. + * + * @param c The JComponent to install. + */ + public void installUI(JComponent c) + { + super.installUI(c); + if (c instanceof JScrollBar) + { + scrollbar = (JScrollBar) c; + + trackRect = new Rectangle(); + thumbRect = new Rectangle(); + + scrollTimer = new Timer(300, null); + + installDefaults(); + installComponents(); + configureScrollBarColors(); + installListeners(); + installKeyboardActions(); + + calculatePreferredSize(); + } + } + + /** + * This method lays out the scrollbar. + * + * @param scrollbarContainer The Container to layout. + */ + public void layoutContainer(Container scrollbarContainer) + { + if (scrollbarContainer instanceof JScrollBar) + { + if (scrollbar.getOrientation() == SwingConstants.HORIZONTAL) + layoutHScrollbar((JScrollBar) scrollbarContainer); + else + layoutVScrollbar((JScrollBar) scrollbarContainer); + } + } + + /** + * This method lays out the scrollbar horizontally. + * + * @param sb The JScrollBar to layout. + */ + protected void layoutHScrollbar(JScrollBar sb) + { + Rectangle vr = new Rectangle(); + SwingUtilities.calculateInnerArea(scrollbar, vr); + + Dimension incrDims = incrButton.getPreferredSize(); + Dimension decrDims = decrButton.getPreferredSize(); + + // calculate and update the track bounds + SwingUtilities.calculateInnerArea(scrollbar, trackRect); + trackRect.width -= incrDims.getWidth(); + trackRect.width -= decrDims.getWidth(); + trackRect.x += decrDims.getWidth(); + + updateThumbRect(); + + decrButton.setBounds(vr.x, vr.y, decrDims.width, trackRect.height); + incrButton.setBounds(trackRect.x + trackRect.width, vr.y, incrDims.width, + trackRect.height); + } + + /** + * This method lays out the scrollbar vertically. + * + * @param sb The JScrollBar to layout. + */ + protected void layoutVScrollbar(JScrollBar sb) + { + Rectangle vr = new Rectangle(); + SwingUtilities.calculateInnerArea(scrollbar, vr); + + Dimension incrDims = incrButton.getPreferredSize(); + Dimension decrDims = decrButton.getPreferredSize(); + + // Update rectangles + SwingUtilities.calculateInnerArea(scrollbar, trackRect); + trackRect.height -= incrDims.getHeight(); + trackRect.height -= decrDims.getHeight(); + trackRect.y += decrDims.getHeight(); + + updateThumbRect(); + + decrButton.setBounds(vr.x, vr.y, trackRect.width, decrDims.height); + incrButton.setBounds(vr.x, trackRect.y + trackRect.height, + trackRect.width, incrDims.height); + } + + /** + * Updates the thumb rect. + */ + void updateThumbRect() + { + int max = scrollbar.getMaximum(); + int min = scrollbar.getMinimum(); + int value = scrollbar.getValue(); + int extent = scrollbar.getVisibleAmount(); + if (max - extent <= min) + { + if (scrollbar.getOrientation() == JScrollBar.HORIZONTAL) + { + thumbRect.x = trackRect.x; + thumbRect.y = trackRect.y; + thumbRect.width = getMinimumThumbSize().width; + thumbRect.height = trackRect.height; + } + else + { + thumbRect.x = trackRect.x; + thumbRect.y = trackRect.y; + thumbRect.width = trackRect.width; + thumbRect.height = getMinimumThumbSize().height; + } + } + else + { + if (scrollbar.getOrientation() == JScrollBar.HORIZONTAL) + { + thumbRect.x = trackRect.x; + thumbRect.width = Math.max(extent * trackRect.width / (max - min), + getMinimumThumbSize().width); + int availableWidth = trackRect.width - thumbRect.width; + thumbRect.x += (value - min) * availableWidth / (max - min - extent); + thumbRect.y = trackRect.y; + thumbRect.height = trackRect.height; + } + else + { + thumbRect.x = trackRect.x; + thumbRect.height = Math.max(extent * trackRect.height / (max - min), + getMinimumThumbSize().height); + int availableHeight = trackRect.height - thumbRect.height; + thumbRect.y = trackRect.y + + (value - min) * availableHeight / (max - min - extent); + thumbRect.width = trackRect.width; + } + } + + } + + /** + * This method returns the minimum size required for the layout. + * + * @param scrollbarContainer The Container that is laid out. + * + * @return The minimum size. + */ + public Dimension minimumLayoutSize(Container scrollbarContainer) + { + return preferredLayoutSize(scrollbarContainer); + } + + /** + * This method is called when the component is painted. + * + * @param g The Graphics object to paint with. + * @param c The JComponent to paint. + */ + public void paint(Graphics g, JComponent c) + { + paintTrack(g, c, getTrackBounds()); + paintThumb(g, c, getThumbBounds()); + + if (trackHighlight == INCREASE_HIGHLIGHT) + paintIncreaseHighlight(g); + else if (trackHighlight == DECREASE_HIGHLIGHT) + paintDecreaseHighlight(g); + } + + /** + * This method is called when repainting and the mouse is pressed in the + * track. It paints the track below the thumb with the trackHighlight + * color. + * + * @param g The Graphics object to paint with. + */ + protected void paintDecreaseHighlight(Graphics g) + { + Color saved = g.getColor(); + + g.setColor(trackHighlightColor); + if (scrollbar.getOrientation() == HORIZONTAL) + g.fillRect(trackRect.x, trackRect.y, thumbRect.x - trackRect.x, + trackRect.height); + else + g.fillRect(trackRect.x, trackRect.y, trackRect.width, + thumbRect.y - trackRect.y); + g.setColor(saved); + } + + /** + * This method is called when repainting and the mouse is pressed in the + * track. It paints the track above the thumb with the trackHighlight + * color. + * + * @param g The Graphics objet to paint with. + */ + protected void paintIncreaseHighlight(Graphics g) + { + Color saved = g.getColor(); + + g.setColor(trackHighlightColor); + if (scrollbar.getOrientation() == HORIZONTAL) + g.fillRect(thumbRect.x + thumbRect.width, trackRect.y, + trackRect.x + trackRect.width - thumbRect.x - thumbRect.width, + trackRect.height); + else + g.fillRect(trackRect.x, thumbRect.y + thumbRect.height, trackRect.width, + trackRect.y + trackRect.height - thumbRect.y + - thumbRect.height); + g.setColor(saved); + } + + /** + * This method paints the thumb. + * + * @param g The Graphics object to paint with. + * @param c The Component that is being painted. + * @param thumbBounds The thumb bounds. + */ + protected void paintThumb(Graphics g, JComponent c, Rectangle thumbBounds) + { + g.setColor(thumbColor); + g.fillRect(thumbBounds.x, thumbBounds.y, thumbBounds.width, + thumbBounds.height); + + BasicGraphicsUtils.drawBezel(g, thumbBounds.x, thumbBounds.y, + thumbBounds.width, thumbBounds.height, + false, false, thumbDarkShadowColor, + thumbDarkShadowColor, thumbHighlightColor, + thumbHighlightColor); + } + + /** + * This method paints the track. + * + * @param g The Graphics object to paint with. + * @param c The JComponent being painted. + * @param trackBounds The track's bounds. + */ + protected void paintTrack(Graphics g, JComponent c, Rectangle trackBounds) + { + Color saved = g.getColor(); + g.setColor(trackColor); + g.fill3DRect(trackBounds.x, trackBounds.y, trackBounds.width, + trackBounds.height, false); + g.setColor(saved); + } + + /** + * This method returns the preferred size for the layout. + * + * @param scrollbarContainer The Container to find a size for. + * + * @return The preferred size for the layout. + */ + public Dimension preferredLayoutSize(Container scrollbarContainer) + { + if (scrollbarContainer instanceof JComponent) + return getPreferredSize((JComponent) scrollbarContainer); + else + return null; + } + + /** + * This method removes a child component from the layout. + * + * @param child The child to remove. + */ + public void removeLayoutComponent(Component child) + { + // You should not be removing stuff from this component. + } + + /** + * The method scrolls the thumb by a block in the direction specified. + * + * @param direction The direction to scroll. + */ + protected void scrollByBlock(int direction) + { + scrollByBlock(scrollbar, direction); + } + + /** + * Scrolls the specified <code>scrollBar</code> by one block (according + * to the scrollable protocol) in the specified <code>direction</code>. + * + * This method is here statically to support wheel scrolling from the + * BasicScrollPaneUI without code duplication. + * + * @param scrollBar the scrollbar to scroll + * @param direction the scroll direction + */ + static final void scrollByBlock(JScrollBar scrollBar, int direction) + { + int delta; + if (direction > 0) + delta = scrollBar.getBlockIncrement(direction); + else + delta = - scrollBar.getBlockIncrement(direction); + int oldValue = scrollBar.getValue(); + int newValue = oldValue + delta; + + // Overflow check. + if (delta > 0 && newValue < oldValue) + newValue = scrollBar.getMaximum(); + else if (delta < 0 && newValue > oldValue) + newValue = scrollBar.getMinimum(); + + scrollBar.setValue(newValue); + } + + /** + * The method scrolls the thumb by a unit in the direction specified. + * + * @param direction The direction to scroll. + */ + protected void scrollByUnit(int direction) + { + scrollByUnits(scrollbar, direction, 1); + } + + /** + * Scrolls the specified <code>scrollbac/code> by <code>units</code> units + * in the specified <code>direction</code>. + * + * This method is here statically to support wheel scrolling from the + * BasicScrollPaneUI without code duplication. + * + * @param scrollBar the scrollbar to scroll + * @param direction the direction + * @param units the number of units to scroll + */ + static final void scrollByUnits(JScrollBar scrollBar, int direction, + int units) + { + // Do this inside a loop so that we don't clash with the scrollable + // interface, which can return different units at times. For instance, + // a Scrollable could return a unit of 2 pixels only to adjust the + // visibility of an item. If we would simply multiply this by units, + // then we would only get 6 pixels, which is complete crap. + for (int i = 0; i < units; i++) + { + int delta; + if (direction > 0) + delta = scrollBar.getUnitIncrement(direction); + else + delta = - scrollBar.getUnitIncrement(direction); + int oldValue = scrollBar.getValue(); + int newValue = oldValue + delta; + + // Overflow check. + if (delta > 0 && newValue < oldValue) + newValue = scrollBar.getMaximum(); + else if (delta < 0 && newValue > oldValue) + newValue = scrollBar.getMinimum(); + + scrollBar.setValue(newValue); + } + } + + /** + * This method sets the thumb's bounds. + * + * @param x The X position of the thumb. + * @param y The Y position of the thumb. + * @param width The width of the thumb. + * @param height The height of the thumb. + */ + protected void setThumbBounds(int x, int y, int width, int height) + { + thumbRect.x = x; + thumbRect.y = y; + thumbRect.width = width; + thumbRect.height = height; + } + + /** + * This method uninstalls any components that are a part of or related to + * this scrollbar. + */ + protected void uninstallComponents() + { + if (incrButton != null) + scrollbar.remove(incrButton); + if (decrButton != null) + scrollbar.remove(decrButton); + } + + /** + * This method uninstalls any defaults that this scrollbar acquired from the + * Basic Look and Feel defaults. + */ + protected void uninstallDefaults() + { + scrollbar.setForeground(null); + scrollbar.setBackground(null); + LookAndFeel.uninstallBorder(scrollbar); + incrButton = null; + decrButton = null; + } + + /** + * This method uninstalls any listeners that were registered during install. + */ + protected void uninstallListeners() + { + if (scrollTimer != null) + scrollTimer.removeActionListener(scrollListener); + + if (scrollbar != null) + { + scrollbar.getModel().removeChangeListener(modelListener); + scrollbar.removePropertyChangeListener(propertyChangeListener); + scrollbar.removeMouseListener(trackListener); + scrollbar.removeMouseMotionListener(trackListener); + } + + if (decrButton != null) + decrButton.removeMouseListener(buttonListener); + if (incrButton != null) + incrButton.removeMouseListener(buttonListener); + + propertyChangeListener = null; + modelListener = null; + buttonListener = null; + trackListener = null; + scrollListener = null; + } + + /** + * This method uninstalls the UI. This includes removing any defaults, + * listeners, and components that this UI may have initialized. It also + * nulls any instance data. + * + * @param c The Component to uninstall for. + */ + public void uninstallUI(JComponent c) + { + uninstallKeyboardActions(); + uninstallListeners(); + uninstallDefaults(); + uninstallComponents(); + + scrollTimer = null; + + thumbRect = null; + trackRect = null; + + trackColor = null; + trackHighlightColor = null; + thumbColor = null; + thumbHighlightColor = null; + thumbDarkShadowColor = null; + thumbLightShadowColor = null; + + scrollbar = null; + } + + /** + * This method returns the value in the scrollbar's range given the y + * coordinate. If the value is out of range, it will return the closest + * legal value. + * This is package-private to avoid an accessor method. + * + * @param yPos The y coordinate to calculate a value for. + * + * @return The value for the y coordinate. + */ + int valueForYPosition(int yPos) + { + int min = scrollbar.getMinimum(); + int max = scrollbar.getMaximum(); + int len = trackRect.height; + + int value; + + // If the length is 0, you shouldn't be able to even see where the thumb is. + // This really shouldn't ever happen, but just in case, we'll return the middle. + if (len == 0) + return (max - min) / 2; + + value = (yPos - trackRect.y) * (max - min) / len + min; + + // If this isn't a legal value, then we'll have to move to one now. + if (value > max) + value = max; + else if (value < min) + value = min; + return value; + } + + /** + * This method returns the value in the scrollbar's range given the x + * coordinate. If the value is out of range, it will return the closest + * legal value. + * This is package-private to avoid an accessor method. + * + * @param xPos The x coordinate to calculate a value for. + * + * @return The value for the x coordinate. + */ + int valueForXPosition(int xPos) + { + int min = scrollbar.getMinimum(); + int max = scrollbar.getMaximum(); + int len = trackRect.width; + + int value; + + // If the length is 0, you shouldn't be able to even see where the slider is. + // This really shouldn't ever happen, but just in case, we'll return the middle. + if (len == 0) + return (max - min) / 2; + + value = (xPos - trackRect.x) * (max - min) / len + min; + + // If this isn't a legal value, then we'll have to move to one now. + if (value > max) + value = max; + else if (value < min) + value = min; + return value; + } + + /** + * Returns true if the mouse is over the thumb. + * + * @return true if the mouse is over the thumb. + * + * @since 1.5 + */ + public boolean isThumbRollover() + { + return thumbRollover; + } + + /** + * Set thumbRollover to active. This indicates + * whether or not the mouse is over the thumb. + * + * @param active - true if the mouse is over the thumb. + * + * @since 1.5 + */ + protected void setThumbRollover(boolean active) + { + thumbRollover = active; + } + + /** + * Indicates whether the user can position the thumb with + * a mouse click (i.e. middle button). + * + * @return true if the user can position the thumb with a mouse + * click. + * + * @since 1.5 + */ + public boolean getSupportsAbsolutePositioning() + { + // The positioning feature has not been implemented. + // So, false is always returned. + return false; + } +} diff --git a/libjava/classpath/javax/swing/plaf/basic/BasicScrollPaneUI.java b/libjava/classpath/javax/swing/plaf/basic/BasicScrollPaneUI.java new file mode 100644 index 000000000..712394830 --- /dev/null +++ b/libjava/classpath/javax/swing/plaf/basic/BasicScrollPaneUI.java @@ -0,0 +1,823 @@ +/* BasicScrollPaneUI.java + Copyright (C) 2002, 2004, 2005, 2006, 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 java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.event.ActionEvent; +import java.awt.event.ContainerEvent; +import java.awt.event.ContainerListener; +import java.awt.event.MouseWheelEvent; +import java.awt.event.MouseWheelListener; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; + +import javax.swing.AbstractAction; +import javax.swing.ActionMap; +import javax.swing.InputMap; +import javax.swing.JComponent; +import javax.swing.JScrollBar; +import javax.swing.JScrollPane; +import javax.swing.JSlider; +import javax.swing.JViewport; +import javax.swing.LookAndFeel; +import javax.swing.ScrollPaneConstants; +import javax.swing.ScrollPaneLayout; +import javax.swing.SwingUtilities; +import javax.swing.UIManager; +import javax.swing.border.Border; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import javax.swing.plaf.ActionMapUIResource; +import javax.swing.plaf.ComponentUI; +import javax.swing.plaf.ScrollPaneUI; +import javax.swing.plaf.UIResource; + +/** + * A UI delegate for the {@link JScrollPane} component. + */ +public class BasicScrollPaneUI extends ScrollPaneUI + implements ScrollPaneConstants +{ + + /** + * Listens for changes in the state of the horizontal scrollbar's model and + * updates the scrollpane accordingly. + * + * @author Roman Kennke (kennke@aicas.com) + */ + public class HSBChangeListener implements ChangeListener + { + + /** + * Receives notification when the state of the horizontal scrollbar + * model has changed. + * + * @param event the change event + */ + public void stateChanged(ChangeEvent event) + { + JScrollBar hsb = scrollpane.getHorizontalScrollBar(); + JViewport vp = scrollpane.getViewport(); + Point viewPosition = vp.getViewPosition(); + viewPosition.x = hsb.getValue(); + vp.setViewPosition(viewPosition); + } + + } + + /** + * Listens for changes in the state of the vertical scrollbar's model and + * updates the scrollpane accordingly. + * + * @author Roman Kennke (kennke@aicas.com) + */ + public class VSBChangeListener implements ChangeListener + { + + /** + * Receives notification when the state of the vertical scrollbar + * model has changed. + * + * @param event the change event + */ + public void stateChanged(ChangeEvent event) + { + JScrollBar vsb = scrollpane.getVerticalScrollBar(); + JViewport vp = scrollpane.getViewport(); + Point viewPosition = vp.getViewPosition(); + viewPosition.y = vsb.getValue(); + vp.setViewPosition(viewPosition); + } + + } + + /** + * Listens for changes of the viewport's extent size and updates the + * scrollpane accordingly. + * + * @author Roman Kennke (kennke@aicas.com) + */ + public class ViewportChangeHandler implements ChangeListener + { + + /** + * Receives notification when the view's size, position or extent size + * changes. When the extents size has changed, this method calls + * {@link BasicScrollPaneUI#syncScrollPaneWithViewport()} to adjust the + * scrollbars extents as well. + * + * @param event the change event + */ + public void stateChanged(ChangeEvent event) + { + syncScrollPaneWithViewport(); + } + + } + + /** + * Listens for property changes on the scrollpane and update the view + * accordingly. + * + * @author Roman Kennke (kennke@aicas.com) + */ + public class PropertyChangeHandler implements PropertyChangeListener + { + + /** + * Receives notification when any of the scrollpane's bound property + * changes. This method calls the appropriate update method on the + * <code>ScrollBarUI</code>. + * + * @param e the property change event + * + * @see BasicScrollPaneUI#updateColumnHeader(PropertyChangeEvent) + * @see BasicScrollPaneUI#updateRowHeader(PropertyChangeEvent) + * @see BasicScrollPaneUI#updateScrollBarDisplayPolicy(PropertyChangeEvent) + * @see BasicScrollPaneUI#updateViewport(PropertyChangeEvent) + */ + public void propertyChange(PropertyChangeEvent e) + { + String propName = e.getPropertyName(); + if (propName.equals("viewport")) + updateViewport(e); + else if (propName.equals("rowHeader")) + updateRowHeader(e); + else if (propName.equals("columnHeader")) + updateColumnHeader(e); + else if (propName.equals("horizontalScrollBarPolicy") + || e.getPropertyName().equals("verticalScrollBarPolicy")) + updateScrollBarDisplayPolicy(e); + else if (propName.equals("verticalScrollBar")) + { + JScrollBar oldSb = (JScrollBar) e.getOldValue(); + oldSb.getModel().removeChangeListener(vsbChangeListener); + JScrollBar newSb = (JScrollBar) e.getNewValue(); + newSb.getModel().addChangeListener(vsbChangeListener); + } + else if (propName.equals("horizontalScrollBar")) + { + JScrollBar oldSb = (JScrollBar) e.getOldValue(); + oldSb.getModel().removeChangeListener(hsbChangeListener); + JScrollBar newSb = (JScrollBar) e.getNewValue(); + newSb.getModel().addChangeListener(hsbChangeListener); + } + } + + } + + /** + * Listens for mouse wheel events and update the scrollpane accordingly. + * + * @author Roman Kennke (kennke@aicas.com) + * + * @since 1.4 + */ + protected class MouseWheelHandler implements MouseWheelListener + { + /** + * Use to compute the visible rectangle. + */ + final Rectangle rect = new Rectangle(); + + /** + * Scroll with the mouse wheel. + * + * @author Audrius Meskauskas (audriusa@Bioinformatics.org) + */ + public void mouseWheelMoved(MouseWheelEvent e) + { + if (scrollpane.isWheelScrollingEnabled() && e.getScrollAmount() != 0) + { + // Try to scroll vertically first. + JScrollBar scrollBar = scrollpane.getVerticalScrollBar(); + if (scrollBar == null || ! scrollBar.isVisible()) + scrollBar = scrollpane.getHorizontalScrollBar(); + if (scrollBar != null && scrollBar.isVisible()) + { + int direction = e.getWheelRotation() < 0 ? -1 : 1; + int scrollType = e.getScrollType(); + if (scrollType == MouseWheelEvent.WHEEL_UNIT_SCROLL) + BasicScrollBarUI.scrollByUnits(scrollBar, direction, + e.getScrollAmount()); + else if (scrollType == MouseWheelEvent.WHEEL_BLOCK_SCROLL) + BasicScrollBarUI.scrollByBlock(scrollBar, direction); + } + } + } + } + + /** + * Adds/removes the mouse wheel listener when the component is added/removed + * to/from the scroll pane view port. + * + * @author Audrius Meskauskas (audriusa@bioinformatics.org) + */ + class ViewportContainerListener implements ContainerListener + { + /** + * Add the mouse wheel listener, allowing to scroll with the mouse. + */ + public void componentAdded(ContainerEvent e) + { + e.getChild().addMouseWheelListener(mouseWheelListener); + } + + /** + * Remove the mouse wheel listener. + */ + public void componentRemoved(ContainerEvent e) + { + e.getChild().removeMouseWheelListener(mouseWheelListener); + } + } + + /** + * The number of pixels by that we should scroll the content that does + * not implement Scrollable. + */ + static int SCROLL_NON_SCROLLABLES = 10; + + /** + * The number of rows to scroll per mouse wheel click. From impression, + * Sun seems using the value 3. + */ + static int ROWS_PER_WHEEL_CLICK = 3; + + /** The Scrollpane for which the UI is provided by this class. */ + protected JScrollPane scrollpane; + + /** + * The horizontal scrollbar listener. + */ + protected ChangeListener hsbChangeListener; + + /** + * The vertical scrollbar listener. + */ + protected ChangeListener vsbChangeListener; + + /** + * The viewport listener. + */ + protected ChangeListener viewportChangeListener; + + /** + * The scrollpane property change listener. + */ + protected PropertyChangeListener spPropertyChangeListener; + + /** + * The mousewheel listener for the scrollpane. + */ + MouseWheelListener mouseWheelListener; + + /** + * The listener to add and remove the mouse wheel listener to/from + * the component container. + */ + ContainerListener containerListener; + + public static ComponentUI createUI(final JComponent c) + { + return new BasicScrollPaneUI(); + } + + protected void installDefaults(JScrollPane p) + { + scrollpane = p; + LookAndFeel.installColorsAndFont(p, "ScrollPane.background", + "ScrollPane.foreground", + "ScrollPane.font"); + LookAndFeel.installBorder(p, "ScrollPane.border"); + + // Install Viewport border. + Border vpBorder = p.getViewportBorder(); + if (vpBorder == null || vpBorder instanceof UIResource) + { + vpBorder = UIManager.getBorder("ScrollPane.viewportBorder"); + p.setViewportBorder(vpBorder); + } + + p.setOpaque(true); + } + + protected void uninstallDefaults(JScrollPane p) + { + LookAndFeel.uninstallBorder(p); + Border vpBorder = p.getViewportBorder(); + if (vpBorder != null && vpBorder instanceof UIResource) + p.setViewportBorder(null); + } + + public void installUI(final JComponent c) + { + super.installUI(c); + installDefaults((JScrollPane) c); + installListeners((JScrollPane) c); + installKeyboardActions((JScrollPane) c); + } + + /** + * Installs the listeners on the scrollbars, the viewport and the scrollpane. + * + * @param sp the scrollpane on which to install the listeners + */ + protected void installListeners(JScrollPane sp) + { + if (spPropertyChangeListener == null) + spPropertyChangeListener = createPropertyChangeListener(); + sp.addPropertyChangeListener(spPropertyChangeListener); + + if (hsbChangeListener == null) + hsbChangeListener = createHSBChangeListener(); + sp.getHorizontalScrollBar().getModel().addChangeListener(hsbChangeListener); + + if (vsbChangeListener == null) + vsbChangeListener = createVSBChangeListener(); + sp.getVerticalScrollBar().getModel().addChangeListener(vsbChangeListener); + + if (viewportChangeListener == null) + viewportChangeListener = createViewportChangeListener(); + + if (mouseWheelListener == null) + mouseWheelListener = createMouseWheelListener(); + + if (containerListener == null) + containerListener = new ViewportContainerListener(); + + JViewport v = sp.getViewport(); + v.addChangeListener(viewportChangeListener); + v.addContainerListener(containerListener); + + // Add mouse wheel listeners to the componets that are probably already + // in the view port. + for (int i = 0; i < v.getComponentCount(); i++) + v.getComponent(i).addMouseWheelListener(mouseWheelListener); + } + + InputMap getInputMap(int condition) + { + if (condition == JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT) + return (InputMap) UIManager.get("ScrollPane.ancestorInputMap"); + return null; + } + + /** + * Returns the action map for the {@link JScrollPane}. All scroll panes + * share a single action map which is created the first time this method is + * called, then stored in the UIDefaults table for subsequent access. + * + * @return The shared action map. + */ + ActionMap getActionMap() + { + ActionMap map = (ActionMap) UIManager.get("ScrollPane.actionMap"); + + if (map == null) // first time here + { + map = createActionMap(); + if (map != null) + UIManager.put("ScrollPane.actionMap", map); + } + return map; + } + + /** + * Creates the action map shared by all {@link JSlider} instances. + * This method is called once by {@link #getActionMap()} when it + * finds no action map in the UIDefaults table...after the map is + * created, it gets added to the defaults table so that subsequent + * calls to {@link #getActionMap()} will return the same shared + * instance. + * + * @return The action map. + */ + ActionMap createActionMap() + { + ActionMap map = new ActionMapUIResource(); + map.put("scrollLeft", + new AbstractAction("scrollLeft") { + public void actionPerformed(ActionEvent event) + { + JScrollPane sp = (JScrollPane) event.getSource(); + JScrollBar sb = sp.getHorizontalScrollBar(); + if (sb.isVisible()) + { + int delta = sb.getBlockIncrement(-1); + sb.setValue(sb.getValue() + delta); + } + } + } + ); + map.put("scrollEnd", + new AbstractAction("scrollEnd") { + public void actionPerformed(ActionEvent event) + { + JScrollPane sp = (JScrollPane) event.getSource(); + JScrollBar sb1 = sp.getHorizontalScrollBar(); + if (sb1.isVisible()) + { + sb1.setValue(sb1.getMaximum()); + } + JScrollBar sb2 = sp.getVerticalScrollBar(); + if (sb2.isVisible()) + { + sb2.setValue(sb2.getMaximum()); + } + } + } + ); + map.put("unitScrollUp", + new AbstractAction("unitScrollUp") { + public void actionPerformed(ActionEvent event) + { + JScrollPane sp = (JScrollPane) event.getSource(); + JScrollBar sb = sp.getVerticalScrollBar(); + if (sb.isVisible()) + { + int delta = sb.getUnitIncrement(-1); + sb.setValue(sb.getValue() + delta); + } + } + } + ); + map.put("unitScrollLeft", + new AbstractAction("unitScrollLeft") { + public void actionPerformed(ActionEvent event) + { + JScrollPane sp = (JScrollPane) event.getSource(); + JScrollBar sb = sp.getHorizontalScrollBar(); + if (sb.isVisible()) + { + int delta = sb.getUnitIncrement(-1); + sb.setValue(sb.getValue() + delta); + } + } + } + ); + map.put("scrollUp", + new AbstractAction("scrollUp") { + public void actionPerformed(ActionEvent event) + { + JScrollPane sp = (JScrollPane) event.getSource(); + JScrollBar sb = sp.getVerticalScrollBar(); + if (sb.isVisible()) + { + int delta = sb.getBlockIncrement(-1); + sb.setValue(sb.getValue() + delta); + } + } + } + ); + map.put("scrollRight", + new AbstractAction("scrollRight") { + public void actionPerformed(ActionEvent event) + { + JScrollPane sp = (JScrollPane) event.getSource(); + JScrollBar sb = sp.getHorizontalScrollBar(); + if (sb.isVisible()) + { + int delta = sb.getBlockIncrement(1); + sb.setValue(sb.getValue() + delta); + } + } + } + ); + map.put("scrollHome", + new AbstractAction("scrollHome") { + public void actionPerformed(ActionEvent event) + { + JScrollPane sp = (JScrollPane) event.getSource(); + JScrollBar sb1 = sp.getHorizontalScrollBar(); + if (sb1.isVisible()) + { + sb1.setValue(sb1.getMinimum()); + } + JScrollBar sb2 = sp.getVerticalScrollBar(); + if (sb2.isVisible()) + { + sb2.setValue(sb2.getMinimum()); + } + } + } + ); + map.put("scrollDown", + new AbstractAction("scrollDown") { + public void actionPerformed(ActionEvent event) + { + JScrollPane sp = (JScrollPane) event.getSource(); + JScrollBar sb = sp.getVerticalScrollBar(); + if (sb.isVisible()) + { + int delta = sb.getBlockIncrement(1); + sb.setValue(sb.getValue() + delta); + } + } + } + ); + map.put("unitScrollDown", + new AbstractAction("unitScrollDown") { + public void actionPerformed(ActionEvent event) + { + JScrollPane sp = (JScrollPane) event.getSource(); + JScrollBar sb = sp.getVerticalScrollBar(); + if (sb.isVisible()) + { + int delta = sb.getUnitIncrement(1); + sb.setValue(sb.getValue() + delta); + } + } + } + ); + map.put("unitScrollRight", + new AbstractAction("unitScrollRight") { + public void actionPerformed(ActionEvent event) + { + JScrollPane sp = (JScrollPane) event.getSource(); + JScrollBar sb = sp.getHorizontalScrollBar(); + if (sb.isVisible()) + { + int delta = sb.getUnitIncrement(1); + sb.setValue(sb.getValue() + delta); + } + } + } + ); + return map; + } + + /** + * Installs additional keyboard actions on the scrollpane. This is a hook + * method provided to subclasses in order to install their own keyboard + * actions. + * + * @param sp the scrollpane to install keyboard actions on + */ + protected void installKeyboardActions(JScrollPane sp) + { + InputMap keyMap = getInputMap( + JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); + SwingUtilities.replaceUIInputMap(sp, + JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, keyMap); + ActionMap map = getActionMap(); + SwingUtilities.replaceUIActionMap(sp, map); + } + + /** + * Uninstalls all keyboard actions from the JScrollPane that have been + * installed by {@link #installKeyboardActions}. This is a hook method + * provided to subclasses to add their own keyboard actions. + * + * @param sp the scrollpane to uninstall keyboard actions from + */ + protected void uninstallKeyboardActions(JScrollPane sp) + { + SwingUtilities.replaceUIActionMap(sp, null); + SwingUtilities.replaceUIInputMap(sp, + JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, null); + } + + /** + * Creates and returns the change listener for the horizontal scrollbar. + * + * @return the change listener for the horizontal scrollbar + */ + protected ChangeListener createHSBChangeListener() + { + return new HSBChangeListener(); + } + + /** + * Creates and returns the change listener for the vertical scrollbar. + * + * @return the change listener for the vertical scrollbar + */ + protected ChangeListener createVSBChangeListener() + { + return new VSBChangeListener(); + } + + /** + * Creates and returns the change listener for the viewport. + * + * @return the change listener for the viewport + */ + protected ChangeListener createViewportChangeListener() + { + return new ViewportChangeHandler(); + } + + /** + * Creates and returns the property change listener for the scrollpane. + * + * @return the property change listener for the scrollpane + */ + protected PropertyChangeListener createPropertyChangeListener() + { + return new PropertyChangeHandler(); + } + + /** + * Creates and returns the mouse wheel listener for the scrollpane. + * + * @return the mouse wheel listener for the scrollpane + * + * @since 1.4 + */ + protected MouseWheelListener createMouseWheelListener() + { + return new MouseWheelHandler(); + } + + public void uninstallUI(final JComponent c) + { + uninstallDefaults((JScrollPane) c); + uninstallListeners(c); + installKeyboardActions((JScrollPane) c); + } + + /** + * Uninstalls all the listeners that have been installed in + * {@link #installListeners(JScrollPane)}. + * + * @param c the scrollpane from which to uninstall the listeners + */ + protected void uninstallListeners(JComponent c) + { + JScrollPane sp = (JScrollPane) c; + sp.removePropertyChangeListener(spPropertyChangeListener); + sp.getHorizontalScrollBar().getModel() + .removeChangeListener(hsbChangeListener); + sp.getVerticalScrollBar().getModel() + .removeChangeListener(vsbChangeListener); + + JViewport v = sp.getViewport(); + v.removeChangeListener(viewportChangeListener); + v.removeContainerListener(containerListener); + + for (int i = 0; i < v.getComponentCount(); i++) + v.getComponent(i).removeMouseWheelListener(mouseWheelListener); + + } + + public Dimension getMinimumSize(JComponent c) + { + JScrollPane p = (JScrollPane) c; + ScrollPaneLayout sl = (ScrollPaneLayout) p.getLayout(); + return sl.minimumLayoutSize(c); + } + + public void paint(Graphics g, JComponent c) + { + Border vpBorder = scrollpane.getViewportBorder(); + if (vpBorder != null) + { + Rectangle r = scrollpane.getViewportBorderBounds(); + vpBorder.paintBorder(scrollpane, g, r.x, r.y, r.width, r.height); + } + } + + /** + * Synchronizes the scrollbar and header settings positions and extent + * with the viewport's view position and extent. + */ + protected void syncScrollPaneWithViewport() + { + JViewport vp = scrollpane.getViewport(); + + if (vp != null) + { + Dimension extentSize = vp.getExtentSize(); + Point viewPos = vp.getViewPosition(); + Dimension viewSize = vp.getViewSize(); + + // Update the vertical scrollbar. + JScrollBar vsb = scrollpane.getVerticalScrollBar(); + if (vsb != null) + { + int extent = extentSize.height; + int max = viewSize.height; + int val = Math.max(0, Math.min(viewPos.y, max - extent)); + vsb.setValues(val, extent, 0, max); + } + + // Update the horizontal scrollbar. + JScrollBar hsb = scrollpane.getHorizontalScrollBar(); + if (hsb != null) + { + int extent = extentSize.width; + int max = viewSize.width; + int val = Math.max(0, Math.min(viewPos.x, max - extent)); + hsb.setValues(val, extent, 0, max); + } + + // Update the row header. + JViewport rowHeader = scrollpane.getRowHeader(); + if (rowHeader != null) + { + Point p = new Point(0, viewPos.y); + rowHeader.setViewPosition(p); + } + + // Update the column header. + JViewport colHeader = scrollpane.getColumnHeader(); + if (colHeader != null) + { + Point p = new Point(viewPos.x, 0); + colHeader.setViewPosition(p); + } + } + } + + /** + * Receives notification when the <code>columnHeader</code> property has + * changed on the scrollpane. + * + * @param ev the property change event + */ + protected void updateColumnHeader(PropertyChangeEvent ev) + { + // TODO: Find out what should be done here. Or is this only a hook? + } + + /** + * Receives notification when the <code>rowHeader</code> property has changed + * on the scrollpane. + * + * @param ev the property change event + */ + protected void updateRowHeader(PropertyChangeEvent ev) + { + // TODO: Find out what should be done here. Or is this only a hook? + } + + /** + * Receives notification when the <code>scrollBarDisplayPolicy</code> + * property has changed on the scrollpane. + * + * @param ev the property change event + */ + protected void updateScrollBarDisplayPolicy(PropertyChangeEvent ev) + { + scrollpane.revalidate(); + scrollpane.repaint(); + } + + /** + * Receives notification when the <code>viewport</code> property has changed + * on the scrollpane. + * + * This method sets removes the viewportChangeListener from the old viewport + * and adds it to the new viewport. + * + * @param ev the property change event + */ + protected void updateViewport(PropertyChangeEvent ev) + { + JViewport oldViewport = (JViewport) ev.getOldValue(); + oldViewport.removeChangeListener(viewportChangeListener); + JViewport newViewport = (JViewport) ev.getNewValue(); + newViewport.addChangeListener(viewportChangeListener); + syncScrollPaneWithViewport(); + } +} diff --git a/libjava/classpath/javax/swing/plaf/basic/BasicSeparatorUI.java b/libjava/classpath/javax/swing/plaf/basic/BasicSeparatorUI.java new file mode 100644 index 000000000..2c80822a4 --- /dev/null +++ b/libjava/classpath/javax/swing/plaf/basic/BasicSeparatorUI.java @@ -0,0 +1,252 @@ +/* BasicSeparatorUI.java -- + Copyright (C) 2004 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 java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Rectangle; + +import javax.swing.JComponent; +import javax.swing.JSeparator; +import javax.swing.SwingUtilities; +import javax.swing.UIManager; +import javax.swing.plaf.ComponentUI; +import javax.swing.plaf.SeparatorUI; + +/** + * The Basic Look and Feel UI delegate for JSeparator. + */ +public class BasicSeparatorUI extends SeparatorUI +{ + /** The shadow color. */ + protected Color shadow; + + /** The highlight color. */ + protected Color highlight; + + /** + * Creates a new UI delegate for the given JComponent. + * + * @param c The JComponent to create a delegate for. + * + * @return A new BasicSeparatorUI. + */ + public static ComponentUI createUI(JComponent c) + { + return new BasicSeparatorUI(); + } + + /** + * This method installs the UI for the given JComponent. + * This can include installing defaults, listeners, and + * initializing any instance data. + * + * @param c The JComponent that is having this UI installed. + */ + public void installUI(JComponent c) + { + super.installUI(c); + + if (c instanceof JSeparator) + { + JSeparator s = (JSeparator) c; + + installDefaults(s); + installListeners(s); + } + } + + /** + * Uninstalls the UI for the given JComponent. This + * method reverses what was done when installing + * the UI on the JComponent. + * + * @param c The JComponent that is having this UI uninstalled. + */ + public void uninstallUI(JComponent c) + { + if (c instanceof JSeparator) + { + JSeparator s = (JSeparator) c; + + uninstallListeners(s); + uninstallDefaults(s); + } + } + + /** + * This method installs the defaults that are given by + * the Basic Look and Feel. + * + * @param s The JSeparator that is being installed. + */ + protected void installDefaults(JSeparator s) + { + shadow = UIManager.getColor("Separator.shadow"); + highlight = UIManager.getColor("Separator.highlight"); + s.setOpaque(false); + } + + /** + * This method removes the defaults that were given + * by the Basic Look and Feel. + * + * @param s The JSeparator that is being uninstalled. + */ + protected void uninstallDefaults(JSeparator s) + { + shadow = null; + highlight = null; + } + + /** + * This method installs any listeners that need + * to be attached to the JSeparator or any of its + * components. + * + * @param s The JSeparator that is being installed. + */ + protected void installListeners(JSeparator s) + { + // Separators don't receive events. + } + + /** + * This method uninstalls any listeners that + * were installed during the install UI process. + * + * @param s The JSeparator that is being uninstalled. + */ + protected void uninstallListeners(JSeparator s) + { + // Separators don't receive events. + } + + /** + * The separator is made of two lines. The top line will be + * the shadow color (or left line if it's vertical). The bottom + * or right line will be the highlight color. The two lines will + * be centered inside the bounds box. If the separator is horizontal, + * then it will be vertically centered, or if it's vertical, it will + * be horizontally centered. + * + * @param g The Graphics object to paint with + * @param c The JComponent to paint. + */ + public void paint(Graphics g, JComponent c) + { + Rectangle r = new Rectangle(); + SwingUtilities.calculateInnerArea(c, r); + Color saved = g.getColor(); + + JSeparator s; + if (c instanceof JSeparator) + s = (JSeparator) c; + else + return; + + if (s.getOrientation() == JSeparator.HORIZONTAL) + { + int midAB = r.height / 2; + g.setColor(shadow); + g.drawLine(r.x, r.y + midAB - 1, r.x + r.width, r.y + midAB - 1); + + g.setColor(highlight); + g.fillRect(r.x, r.y + midAB, r.x + r.width, r.y + midAB); + } + else + { + int midAD = r.height / 2 + r.y; + g.setColor(shadow); + g.drawLine(r.x, r.y, r.x, r.y + r.height); + + g.setColor(highlight); + g.fillRect(r.x + midAD, r.y + r.height, r.x + midAD, r.y + r.height); + } + g.setColor(saved); + } + + /** + * This method returns the preferred size of the + * JComponent. + * + * @param c The JComponent to measure. + * + * @return The preferred size. + */ + public Dimension getPreferredSize(JComponent c) + { + Dimension pref = new Dimension(2, 0); + if (c instanceof JSeparator) + { + JSeparator s = (JSeparator) c; + if (s.getOrientation() == JSeparator.HORIZONTAL) + pref = new Dimension(0, 2); + } + return pref; + } + + /** + * This method returns the minimum size of the + * JComponent. + * + * @param c The JComponent to measure. + * + * @return The minimum size. + */ + public Dimension getMinimumSize(JComponent c) + { + return new Dimension(0, 0); + } + + /** + * This method returns the maximum size of the + * JComponent. + * + * @param c The JComponent to measure. + * + * @return The maximum size. + */ + public Dimension getMaximumSize(JComponent c) + { + return new Dimension(Short.MAX_VALUE, + Short.MAX_VALUE); + } +} diff --git a/libjava/classpath/javax/swing/plaf/basic/BasicSliderUI.java b/libjava/classpath/javax/swing/plaf/basic/BasicSliderUI.java new file mode 100644 index 000000000..b9d564372 --- /dev/null +++ b/libjava/classpath/javax/swing/plaf/basic/BasicSliderUI.java @@ -0,0 +1,2339 @@ +/* BasicSliderUI.java -- + Copyright (C) 2004, 2005, 2006, 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 java.awt.Color; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Insets; +import java.awt.Point; +import java.awt.Polygon; +import java.awt.Rectangle; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.ComponentAdapter; +import java.awt.event.ComponentEvent; +import java.awt.event.ComponentListener; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; +import java.awt.event.MouseEvent; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.Dictionary; +import java.util.Enumeration; + +import javax.swing.AbstractAction; +import javax.swing.ActionMap; +import javax.swing.BoundedRangeModel; +import javax.swing.InputMap; +import javax.swing.JComponent; +import javax.swing.JSlider; +import javax.swing.LookAndFeel; +import javax.swing.SwingUtilities; +import javax.swing.Timer; +import javax.swing.UIManager; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import javax.swing.event.MouseInputAdapter; +import javax.swing.plaf.ActionMapUIResource; +import javax.swing.plaf.ComponentUI; +import javax.swing.plaf.SliderUI; + +/** + * <p> + * BasicSliderUI.java This is the UI delegate in the Basic look and feel that + * paints JSliders. + * </p> + * + * <p> + * The UI delegate keeps track of 6 rectangles that place the various parts of + * the JSlider inside the component. + * </p> + * + * <p> + * The rectangles are organized as follows: + * </p> + * <pre> + * +-------------------------------------------------------+ <-- focusRect + * | | + * | +==+-------------------+==+--------------------+==+<------ contentRect + * | | | | |<---thumbRect | | | + * | | | TRACK | | |<--------- trackRect + * | | +-------------------+==+--------------------+ | | + * | | | | | | + * | | | TICKS GO HERE |<-------- tickRect + * | | | | | | + * | +==+-------------------------------------------+==+ | + * | | | | | | + * | | | | |<----- labelRect + * | | | LABELS GO HERE | | | + * | | | | | | + * | | | | | | + * | | | | | | + * | | | | | | + * | | | | | + * </pre> + * + * <p> + * The space between the contentRect and the focusRect are the FocusInsets. + * </p> + * + * <p> + * The space between the focusRect and the component bounds is the insetCache + * which are the component's insets. + * </p> + * + * <p> + * The top of the thumb is the top of the contentRect. The trackRect has to be + * as tall as the thumb. + * </p> + * + * <p> + * The trackRect and tickRect do not start from the left edge of the + * focusRect. They are trackBuffer away from each side of the focusRect. This + * is so that the thumb has room to move. + * </p> + * + * <p> + * The labelRect does start right against the contentRect's left and right + * edges and it gets all remaining space. + * </p> + */ +public class BasicSliderUI extends SliderUI +{ + /** + * Helper class that listens to the {@link JSlider}'s model for changes. + * + * @specnote Apparently this class was intended to be protected, + * but was made public by a compiler bug and is now + * public for compatibility. + */ + public class ChangeHandler implements ChangeListener + { + /** + * Called when the slider's model has been altered. The UI delegate should + * recalculate any rectangles that are dependent on the model for their + * positions and repaint. + * + * @param e A static {@link ChangeEvent} passed from the model. + */ + public void stateChanged(ChangeEvent e) + { + // Maximum, minimum, and extent values will be taken + // care of automatically when the slider is repainted. + // Only thing that needs recalculation is the thumb. + calculateThumbLocation(); + slider.repaint(); + } + } + + /** + * Helper class that listens for resize events. + * + * @specnote Apparently this class was intended to be protected, + * but was made public by a compiler bug and is now + * public for compatibility. + */ + public class ComponentHandler extends ComponentAdapter + { + /** + * Called when the size of the component changes. The UI delegate should + * recalculate any rectangles that are dependent on the model for their + * positions and repaint. + * + * @param e A {@link ComponentEvent}. + */ + public void componentResized(ComponentEvent e) + { + calculateGeometry(); + slider.repaint(); + } + } + + /** + * Helper class that listens for focus events. + * + * @specnote Apparently this class was intended to be protected, + * but was made public by a compiler bug and is now + * public for compatibility. + */ + public class FocusHandler implements FocusListener + { + /** + * Called when the {@link JSlider} has gained focus. It should repaint + * the slider with the focus drawn. + * + * @param e A {@link FocusEvent}. + */ + public void focusGained(FocusEvent e) + { + slider.repaint(); + } + + /** + * Called when the {@link JSlider} has lost focus. It should repaint the + * slider without the focus drawn. + * + * @param e A {@link FocusEvent}. + */ + public void focusLost(FocusEvent e) + { + slider.repaint(); + } + } + + /** + * Helper class that listens for changes to the properties of the {@link + * JSlider}. + */ + public class PropertyChangeHandler implements PropertyChangeListener + { + /** + * Called when one of the properties change. The UI should recalculate any + * rectangles if necessary and repaint. + * + * @param e A {@link PropertyChangeEvent}. + */ + public void propertyChange(PropertyChangeEvent e) + { + // Check for orientation changes. + String prop = e.getPropertyName(); + if (prop.equals("orientation") + || prop.equals("inverted") + || prop.equals("labelTable") + || prop.equals("majorTickSpacing") + || prop.equals("minorTickSpacing") + || prop.equals("paintTicks") + || prop.equals("paintTrack") + || prop.equals("paintLabels")) + { + calculateGeometry(); + slider.repaint(); + } + else if (e.getPropertyName().equals("model")) + { + BoundedRangeModel oldModel = (BoundedRangeModel) e.getOldValue(); + oldModel.removeChangeListener(changeListener); + slider.getModel().addChangeListener(changeListener); + calculateThumbLocation(); + slider.repaint(); + } + } + } + + /** + * Helper class that listens to our swing timer. This class is responsible + * for listening to the timer and moving the thumb in the proper direction + * every interval. + * + * @specnote Apparently this class was intended to be protected, + * but was made public by a compiler bug and is now + * public for compatibility. + */ + public class ScrollListener implements ActionListener + { + /** Indicates which direction the thumb should scroll. */ + private transient int direction; + + /** Indicates whether we should scroll in blocks or in units. */ + private transient boolean block; + + /** + * Creates a new ScrollListener object. + */ + public ScrollListener() + { + direction = POSITIVE_SCROLL; + block = false; + } + + /** + * Creates a new ScrollListener object. + * + * @param dir The direction to scroll in. + * @param block If movement will be in blocks. + */ + public ScrollListener(int dir, boolean block) + { + direction = dir; + this.block = block; + } + + /** + * Called every time the swing timer reaches its interval. If the thumb + * needs to move, then this method will move the thumb one block or unit + * in the direction desired. Otherwise, the timer can be stopped. + * + * @param e An {@link ActionEvent}. + */ + public void actionPerformed(ActionEvent e) + { + if (! trackListener.shouldScroll(direction)) + { + scrollTimer.stop(); + return; + } + + if (block) + scrollByBlock(direction); + else + scrollByUnit(direction); + } + + /** + * Sets the direction to scroll in. + * + * @param direction The direction to scroll in. + */ + public void setDirection(int direction) + { + this.direction = direction; + } + + /** + * Sets whether movement will be in blocks. + * + * @param block If movement will be in blocks. + */ + public void setScrollByBlock(boolean block) + { + this.block = block; + } + } + + /** + * Helper class that listens for mouse events. + * + * @specnote Apparently this class was intended to be protected, + * but was made public by a compiler bug and is now + * public for compatibility. + */ + public class TrackListener extends MouseInputAdapter + { + /** The current X position of the mouse. */ + protected int currentMouseX; + + /** The current Y position of the mouse. */ + protected int currentMouseY; + + /** + * The offset between the current slider value and the cursor's position. + */ + protected int offset; + + /** + * Called when the mouse has been dragged. This should find the mouse's + * current position and adjust the value of the {@link JSlider} + * accordingly. + * + * @param e A {@link MouseEvent} + */ + public void mouseDragged(MouseEvent e) + { + dragging = true; + if (slider.isEnabled()) + { + currentMouseX = e.getX(); + currentMouseY = e.getY(); + if (slider.getValueIsAdjusting()) + { + int value; + if (slider.getOrientation() == JSlider.HORIZONTAL) + value = valueForXPosition(currentMouseX) - offset; + else + value = valueForYPosition(currentMouseY) - offset; + + slider.setValue(value); + } + } + } + + /** + * Called when the mouse has moved over a component but no buttons have + * been pressed yet. + * + * @param e A {@link MouseEvent} + */ + public void mouseMoved(MouseEvent e) + { + // Don't care that we're moved unless we're dragging. + } + + /** + * Called when the mouse is pressed. When the press occurs on the thumb + * itself, the {@link JSlider} should have its value set to where the + * mouse was pressed. If the press occurs on the track, then the thumb + * should move one block towards the direction of the mouse. + * + * @param e A {@link MouseEvent} + */ + public void mousePressed(MouseEvent e) + { + if (slider.isEnabled()) + { + currentMouseX = e.getX(); + currentMouseY = e.getY(); + + int value; + if (slider.getOrientation() == JSlider.HORIZONTAL) + value = valueForXPosition(currentMouseX); + else + value = valueForYPosition(currentMouseY); + + if (slider.getSnapToTicks()) + value = findClosestTick(value); + + // If the thumb is hit, then we don't need to set the timers to + // move it. + if (! thumbRect.contains(e.getPoint())) + { + // The mouse has hit some other part of the slider. + // The value moves no matter where in the slider you hit. + if (value > slider.getValue()) + scrollDueToClickInTrack(POSITIVE_SCROLL); + else + scrollDueToClickInTrack(NEGATIVE_SCROLL); + } + else + { + slider.setValueIsAdjusting(true); + offset = value - slider.getValue(); + } + } + } + + /** + * Called when the mouse is released. This should stop the timer that + * scrolls the thumb. + * + * @param e A {@link MouseEvent} + */ + public void mouseReleased(MouseEvent e) + { + dragging = false; + if (slider.isEnabled()) + { + currentMouseX = e.getX(); + currentMouseY = e.getY(); + + if (slider.getValueIsAdjusting()) + { + slider.setValueIsAdjusting(false); + if (slider.getSnapToTicks()) + slider.setValue(findClosestTick(slider.getValue())); + } + if (scrollTimer != null) + scrollTimer.stop(); + } + slider.repaint(); + } + + /** + * Indicates whether the thumb should scroll in the given direction. + * + * @param direction The direction to check. + * + * @return True if the thumb should move in that direction. + */ + public boolean shouldScroll(int direction) + { + int value; + if (slider.getOrientation() == JSlider.HORIZONTAL) + value = valueForXPosition(currentMouseX); + else + value = valueForYPosition(currentMouseY); + + if (direction == POSITIVE_SCROLL) + return value > slider.getValue(); + else + return value < slider.getValue(); + } + } + + /** + * This class is no longer used as of JDK1.3. + */ + public class ActionScroller extends AbstractAction + { + /** + * Not used. + * + * @param slider not used + * @param dir not used + * @param block not used + */ + public ActionScroller(JSlider slider, int dir, boolean block) + { + // Not used. + } + + /** + * Not used. + * + * @param event not used + */ + public void actionPerformed(ActionEvent event) + { + // Not used. + } + } + + /** Listener for changes from the model. */ + protected ChangeListener changeListener; + + /** Listener for changes to the {@link JSlider}. */ + protected PropertyChangeListener propertyChangeListener; + + /** Listener for the scrollTimer. */ + protected ScrollListener scrollListener; + + /** Listener for component resizing. */ + protected ComponentListener componentListener; + + /** Listener for focus handling. */ + protected FocusListener focusListener; + + /** Listener for mouse events. */ + protected TrackListener trackListener; + + /** The insets between the FocusRectangle and the ContentRectangle. */ + protected Insets focusInsets; + + /** The {@link JSlider}'s insets. */ + protected Insets insetCache; + + /** Rectangle describing content bounds. See diagram above. */ + protected Rectangle contentRect; + + /** Rectangle describing focus bounds. See diagram above. */ + protected Rectangle focusRect; + + /** Rectangle describing the thumb's bounds. See diagram above. */ + protected Rectangle thumbRect; + + /** Rectangle describing the tick bounds. See diagram above. */ + protected Rectangle tickRect; + + /** Rectangle describing the label bounds. See diagram above. */ + protected Rectangle labelRect; + + /** Rectangle describing the track bounds. See diagram above. */ + protected Rectangle trackRect; + + /** FIXME: use this somewhere. */ + public static final int MAX_SCROLL = 2; + + /** FIXME: use this somewhere. */ + public static final int MIN_SCROLL = -2; + + /** A constant describing scrolling towards the minimum. */ + public static final int NEGATIVE_SCROLL = -1; + + /** A constant describing scrolling towards the maximum. */ + public static final int POSITIVE_SCROLL = 1; + + /** The gap between the edges of the contentRect and trackRect. */ + protected int trackBuffer; + + /** Whether this slider is actually drawn left to right. */ + protected boolean leftToRightCache; + + /** A timer that periodically moves the thumb. */ + protected Timer scrollTimer; + + /** A reference to the {@link JSlider} that this UI was created for. */ + protected JSlider slider; + + /** The shadow color. */ + private transient Color shadowColor; + + /** The highlight color. */ + private transient Color highlightColor; + + /** The focus color. */ + private transient Color focusColor; + + /** True if the user is dragging the slider. */ + boolean dragging; + + /** + * Creates a new Basic look and feel Slider UI. + * + * @param b The {@link JSlider} that this UI was created for. + */ + public BasicSliderUI(JSlider b) + { + super(); + } + + /** + * Returns true if the user is dragging the slider. + * + * @return true if the slider is being dragged. + * + * @since 1.5 + */ + protected boolean isDragging() + { + return dragging; + } + + /** + * Gets the shadow color to be used for this slider. The shadow color is the + * color used for drawing the top and left edges of the track. + * + * @return The shadow color. + */ + protected Color getShadowColor() + { + return shadowColor; + } + + /** + * Gets the highlight color to be used for this slider. The highlight color + * is the color used for drawing the bottom and right edges of the track. + * + * @return The highlight color. + */ + protected Color getHighlightColor() + { + return highlightColor; + } + + /** + * Gets the focus color to be used for this slider. The focus color is the + * color used for drawing the focus rectangle when the component gains + * focus. + * + * @return The focus color. + */ + protected Color getFocusColor() + { + return focusColor; + } + + /** + * Factory method to create a BasicSliderUI for the given {@link + * JComponent}, which should be a {@link JSlider}. + * + * @param b The {@link JComponent} a UI is being created for. + * + * @return A BasicSliderUI for the {@link JComponent}. + */ + public static ComponentUI createUI(JComponent b) + { + return new BasicSliderUI((JSlider) b); + } + + /** + * Installs and initializes all fields for this UI delegate. Any properties + * of the UI that need to be initialized and/or set to defaults will be + * done now. It will also install any listeners necessary. + * + * @param c The {@link JComponent} that is having this UI installed. + */ + public void installUI(JComponent c) + { + super.installUI(c); + if (c instanceof JSlider) + { + slider = (JSlider) c; + + focusRect = new Rectangle(); + contentRect = new Rectangle(); + thumbRect = new Rectangle(); + trackRect = new Rectangle(); + tickRect = new Rectangle(); + labelRect = new Rectangle(); + + insetCache = slider.getInsets(); + leftToRightCache = ! slider.getInverted(); + + scrollTimer = new Timer(200, null); + scrollTimer.setRepeats(true); + + installDefaults(slider); + installListeners(slider); + installKeyboardActions(slider); + + calculateFocusRect(); + + calculateContentRect(); + calculateThumbSize(); + calculateTrackBuffer(); + calculateTrackRect(); + calculateThumbLocation(); + + calculateTickRect(); + calculateLabelRect(); + } + } + + /** + * Performs the opposite of installUI. Any properties or resources that need + * to be cleaned up will be done now. It will also uninstall any listeners + * it has. In addition, any properties of this UI will be nulled. + * + * @param c The {@link JComponent} that is having this UI uninstalled. + */ + public void uninstallUI(JComponent c) + { + super.uninstallUI(c); + + uninstallKeyboardActions(slider); + uninstallListeners(slider); + + scrollTimer = null; + + focusRect = null; + contentRect = null; + thumbRect = null; + trackRect = null; + tickRect = null; + labelRect = null; + + focusInsets = null; + } + + /** + * Initializes any default properties that this UI has from the defaults for + * the Basic look and feel. + * + * @param slider The {@link JSlider} that is having this UI installed. + */ + protected void installDefaults(JSlider slider) + { + LookAndFeel.installColors(slider, "Slider.background", + "Slider.foreground"); + LookAndFeel.installBorder(slider, "Slider.border"); + shadowColor = UIManager.getColor("Slider.shadow"); + highlightColor = UIManager.getColor("Slider.highlight"); + focusColor = UIManager.getColor("Slider.focus"); + focusInsets = UIManager.getInsets("Slider.focusInsets"); + slider.setOpaque(true); + } + + /** + * Creates a new {@link TrackListener}. + * + * @param slider The {@link JSlider} that this {@link TrackListener} is + * created for. + * + * @return A new {@link TrackListener}. + */ + protected TrackListener createTrackListener(JSlider slider) + { + return new TrackListener(); + } + + /** + * Creates a new {@link ChangeListener}. + * + * @param slider The {@link JSlider} that this {@link ChangeListener} is + * created for. + * + * @return A new {@link ChangeListener}. + */ + protected ChangeListener createChangeListener(JSlider slider) + { + return new ChangeHandler(); + } + + /** + * Creates a new {@link ComponentListener}. + * + * @param slider The {@link JSlider} that this {@link ComponentListener} is + * created for. + * + * @return A new {@link ComponentListener}. + */ + protected ComponentListener createComponentListener(JSlider slider) + { + return new ComponentHandler(); + } + + /** + * Creates a new {@link FocusListener}. + * + * @param slider The {@link JSlider} that this {@link FocusListener} is + * created for. + * + * @return A new {@link FocusListener}. + */ + protected FocusListener createFocusListener(JSlider slider) + { + return new FocusHandler(); + } + + /** + * Creates a new {@link ScrollListener}. + * + * @param slider The {@link JSlider} that this {@link ScrollListener} is + * created for. + * + * @return A new {@link ScrollListener}. + */ + protected ScrollListener createScrollListener(JSlider slider) + { + return new ScrollListener(); + } + + /** + * Creates a new {@link PropertyChangeListener}. + * + * @param slider The {@link JSlider} that this {@link + * PropertyChangeListener} is created for. + * + * @return A new {@link PropertyChangeListener}. + */ + protected PropertyChangeListener createPropertyChangeListener(JSlider slider) + { + return new PropertyChangeHandler(); + } + + /** + * Creates and registers all the listeners for this UI delegate. This + * includes creating the ScrollListener and registering it to the timer. + * + * @param slider The {@link JSlider} is having listeners installed. + */ + protected void installListeners(JSlider slider) + { + propertyChangeListener = createPropertyChangeListener(slider); + componentListener = createComponentListener(slider); + trackListener = createTrackListener(slider); + focusListener = createFocusListener(slider); + changeListener = createChangeListener(slider); + scrollListener = createScrollListener(slider); + + slider.addPropertyChangeListener(propertyChangeListener); + slider.addComponentListener(componentListener); + slider.addMouseListener(trackListener); + slider.addMouseMotionListener(trackListener); + slider.addFocusListener(focusListener); + slider.getModel().addChangeListener(changeListener); + + scrollTimer.addActionListener(scrollListener); + } + + /** + * Unregisters all the listeners that this UI delegate was using. In + * addition, it will also null any listeners that it was using. + * + * @param slider The {@link JSlider} that is having listeners removed. + */ + protected void uninstallListeners(JSlider slider) + { + slider.removePropertyChangeListener(propertyChangeListener); + slider.removeComponentListener(componentListener); + slider.removeMouseListener(trackListener); + slider.removeMouseMotionListener(trackListener); + slider.removeFocusListener(focusListener); + slider.getModel().removeChangeListener(changeListener); + + scrollTimer.removeActionListener(scrollListener); + + propertyChangeListener = null; + componentListener = null; + trackListener = null; + focusListener = null; + changeListener = null; + scrollListener = null; + } + + /** + * Installs any keyboard actions. The list of keys that need to be bound are + * listed in Basic look and feel's defaults. + * + * @param slider The {@link JSlider} that is having keyboard actions + * installed. + */ + protected void installKeyboardActions(JSlider slider) + { + InputMap keyMap = getInputMap(JComponent.WHEN_FOCUSED); + SwingUtilities.replaceUIInputMap(slider, JComponent.WHEN_FOCUSED, keyMap); + ActionMap map = getActionMap(); + SwingUtilities.replaceUIActionMap(slider, map); + } + + /** + * Uninstalls any keyboard actions. The list of keys used are listed in + * Basic look and feel's defaults. + * + * @param slider The {@link JSlider} that is having keyboard actions + * uninstalled. + */ + protected void uninstallKeyboardActions(JSlider slider) + { + SwingUtilities.replaceUIActionMap(slider, null); + SwingUtilities.replaceUIInputMap(slider, JComponent.WHEN_FOCUSED, null); + } + + /* XXX: This is all after experimentation with SUN's implementation. + + PreferredHorizontalSize seems to be 200x21. + PreferredVerticalSize seems to be 21x200. + + MinimumHorizontalSize seems to be 36x21. + MinimumVerticalSize seems to be 21x36. + + PreferredSize seems to be 200x63. Or Components.getBounds? + + MinimumSize seems to be 36x63. + + MaximumSize seems to be 32767x63. + */ + + /** + * This method returns the preferred size when the slider is horizontally + * oriented. + * + * @return The dimensions of the preferred horizontal size. + */ + public Dimension getPreferredHorizontalSize() + { + Dimension dim = UIManager.getDimension("Slider.horizontalSize"); + if (dim == null) // Just to be sure we mirror the default. + dim = new Dimension(200, 21); + return dim; + } + + /** + * This method returns the preferred size when the slider is vertically + * oriented. + * + * @return The dimensions of the preferred vertical size. + */ + public Dimension getPreferredVerticalSize() + { + Dimension dim = UIManager.getDimension("Slider.verticalSize"); + if (dim == null) // Just to be sure we mirror the default. + dim = new Dimension(21, 200); + return dim; + } + + /** + * This method returns the minimum size when the slider is horizontally + * oriented. + * + * @return The dimensions of the minimum horizontal size. + */ + public Dimension getMinimumHorizontalSize() + { + Dimension dim = UIManager.getDimension("Slider.minimumHorizontalSize"); + if (dim == null) // Just to be sure we mirror the default. + dim = new Dimension(36, 21); + return dim; + } + + /** + * This method returns the minimum size of the slider when it is vertically + * oriented. + * + * @return The dimensions of the minimum vertical size. + */ + public Dimension getMinimumVerticalSize() + { + Dimension dim = UIManager.getDimension("Slider.minimumVerticalSize"); + if (dim == null) // Just to be sure we mirror the default. + dim = new Dimension(21, 36); + return dim; + } + + /** + * This method returns the preferred size of the component. If it returns + * null, then it is up to the Layout Manager to give the {@link JComponent} + * a size. + * + * @param c The {@link JComponent} to find the preferred size for. + * + * @return The dimensions of the preferred size. + */ + public Dimension getPreferredSize(JComponent c) + { + recalculateIfInsetsChanged(); + Dimension dim; + if (slider.getOrientation() == JSlider.HORIZONTAL) + { + // Create copy here to protect the UIManager value. + dim = new Dimension(getPreferredHorizontalSize()); + dim.height = insetCache.top + insetCache.bottom; + dim.height += focusInsets.top + focusInsets.bottom; + dim.height += trackRect.height + tickRect.height + labelRect.height; + } + else + { + // Create copy here to protect the UIManager value. + dim = new Dimension(getPreferredVerticalSize()); + dim.width = insetCache.left + insetCache.right; + dim.width += focusInsets.left + focusInsets.right; + dim.width += trackRect.width + tickRect.width + labelRect.width; + } + return dim; + } + + /** + * This method returns the minimum size for this {@link JSlider} for this + * look and feel. If it returns null, then it is up to the Layout Manager + * to give the {@link JComponent} a size. + * + * @param c The {@link JComponent} to find the minimum size for. + * + * @return The dimensions of the minimum size. + */ + public Dimension getMinimumSize(JComponent c) + { + recalculateIfInsetsChanged(); + Dimension dim; + if (slider.getOrientation() == JSlider.HORIZONTAL) + { + // Create copy here to protect the UIManager value. + dim = new Dimension(getMinimumHorizontalSize()); + dim.height = insetCache.top + insetCache.bottom; + dim.height += focusInsets.top + focusInsets.bottom; + dim.height += trackRect.height + tickRect.height + labelRect.height; + } + else + { + // Create copy here to protect the UIManager value. + dim = new Dimension(getMinimumVerticalSize()); + dim.width = insetCache.left + insetCache.right; + dim.width += focusInsets.left + focusInsets.right; + dim.width += trackRect.width + tickRect.width + labelRect.width; + } + return dim; + } + + /** + * This method returns the maximum size for this {@link JSlider} for this + * look and feel. + * + * @param c The {@link JComponent} to find a maximum size for. + * + * @return The dimensions of the maximum size. + */ + public Dimension getMaximumSize(JComponent c) + { + Dimension dim = getPreferredSize(c); + if (slider.getOrientation() == JSlider.HORIZONTAL) + dim.width = Short.MAX_VALUE; + else + dim.height = Short.MAX_VALUE; + return dim; + } + + /** + * This method calculates all the sizes of the rectangles by delegating to + * the helper methods calculateXXXRect. + */ + protected void calculateGeometry() + { + calculateFocusRect(); + calculateContentRect(); + calculateThumbSize(); + calculateTrackBuffer(); + calculateTrackRect(); + calculateTickRect(); + calculateLabelRect(); + calculateThumbLocation(); + } + + /** + * This method calculates the size and position of the focusRect. This + * method does not need to be called if the orientation changes. + */ + protected void calculateFocusRect() + { + focusRect.x = insetCache.left; + focusRect.y = insetCache.top; + focusRect.width = slider.getWidth() - insetCache.left - insetCache.right; + focusRect.height = slider.getHeight() - insetCache.top - insetCache.bottom; + } + + /** + * Sets the width and height of the <code>thumbRect</code> field, using the + * dimensions returned by {@link #getThumbSize()}. + */ + protected void calculateThumbSize() + { + Dimension d = getThumbSize(); + thumbRect.width = d.width; + thumbRect.height = d.height; + } + + /** + * Updates the <code>contentRect</code> field to an area inside the + * <code>focusRect</code>. This method does not need to be called if the + * orientation changes. + */ + protected void calculateContentRect() + { + contentRect.x = focusRect.x + focusInsets.left; + contentRect.y = focusRect.y + focusInsets.top; + + contentRect.width = focusRect.width - focusInsets.left - focusInsets.right; + contentRect.height = focusRect.height - focusInsets.top + - focusInsets.bottom; + } + + /** + * Calculates the position of the thumbRect based on the current value of + * the slider. It must take into account the orientation of the slider. + */ + protected void calculateThumbLocation() + { + int value = slider.getValue(); + + if (slider.getOrientation() == JSlider.HORIZONTAL) + { + thumbRect.x = xPositionForValue(value) - thumbRect.width / 2; + thumbRect.y = trackRect.y + 1; + } + else + { + thumbRect.x = trackRect.x + 1; + thumbRect.y = yPositionForValue(value) - thumbRect.height / 2; + } + } + + /** + * Calculates the gap size between the edge of the <code>contentRect</code> + * and the edge of the <code>trackRect</code>, storing the result in the + * <code>trackBuffer</code> field. Sufficient space needs to be reserved + * for the slider thumb and/or the labels at each end of the slider track. + */ + protected void calculateTrackBuffer() + { + if (slider.getOrientation() == JSlider.HORIZONTAL) + { + int w = Math.max(getWidthOfLowValueLabel(), getWidthOfHighValueLabel()); + trackBuffer = Math.max(thumbRect.width / 2, w / 2); + + } + else + { + int h = Math.max(getHeightOfLowValueLabel(), + getHeightOfHighValueLabel()); + trackBuffer = Math.max(thumbRect.height / 2, h / 2); + } + } + + /** + * Returns the size of the slider's thumb. The size is hard coded to + * <code>11 x 20</code> for horizontal sliders, and <code>20 x 11</code> for + * vertical sliders. Note that a new instance of {@link Dimension} is + * returned for every call to this method (this seems wasteful, but + * {@link Dimension} instances are not immutable, so this is probably + * unavoidable). + * + * @return The size of the slider's thumb. + */ + protected Dimension getThumbSize() + { + if (slider.getOrientation() == JSlider.HORIZONTAL) + return new Dimension(11, 20); + else + return new Dimension(20, 11); + } + + /** + * Calculates the size and position of the trackRect. It must take into + * account the orientation of the slider. + */ + protected void calculateTrackRect() + { + if (slider.getOrientation() == JSlider.HORIZONTAL) + { + int center = thumbRect.height; + if (slider.getPaintTicks()) + center += getTickLength(); + if (slider.getPaintLabels()) + center += getHeightOfTallestLabel(); + trackRect.x = contentRect.x + trackBuffer; + trackRect.y = contentRect.y + (contentRect.height - center - 1) / 2; + trackRect.width = contentRect.width - 2 * trackBuffer; + trackRect.height = thumbRect.height; + } + else + { + int center = thumbRect.width; + if (slider.getPaintTicks()) + center += getTickLength(); + if (slider.getPaintLabels()) + center += getWidthOfWidestLabel(); + trackRect.x = contentRect.x + (contentRect.width - center - 1) / 2; + trackRect.y = contentRect.y + trackBuffer; + trackRect.width = thumbRect.width; + trackRect.height = contentRect.height - 2 * trackBuffer; + } + } + + /** + * This method returns the height of the tick area box if the slider is + * horizontal and the width of the tick area box is the slider is vertical. + * It not necessarily how long the ticks will be. If a gap between the edge + * of tick box and the actual tick is desired, then that will need to be + * handled in the tick painting methods. + * + * @return The height (or width if the slider is vertical) of the tick + * rectangle. + */ + protected int getTickLength() + { + return 8; + } + + /** + * This method calculates the size and position of the tickRect. It must + * take into account the orientation of the slider. + */ + protected void calculateTickRect() + { + if (slider.getOrientation() == JSlider.HORIZONTAL) + { + tickRect.x = trackRect.x; + tickRect.y = trackRect.y + trackRect.height; + tickRect.width = trackRect.width; + tickRect.height = getTickLength(); + + // this makes our Mauve tests pass...can't explain it! + if (!slider.getPaintTicks()) + { + tickRect.y--; + tickRect.height = 0; + } + } + else + { + tickRect.x = trackRect.x + trackRect.width; + tickRect.y = trackRect.y; + tickRect.width = getTickLength(); + tickRect.height = trackRect.height; + + // this makes our Mauve tests pass...can't explain it! + if (!slider.getPaintTicks()) + { + tickRect.x--; + tickRect.width = 0; + } + } + } + + /** + * Calculates the <code>labelRect</code> field, taking into account the + * orientation of the slider. + */ + protected void calculateLabelRect() + { + if (slider.getOrientation() == JSlider.HORIZONTAL) + { + if (slider.getPaintLabels()) + { + labelRect.x = tickRect.x - trackBuffer; + labelRect.y = tickRect.y + tickRect.height; + labelRect.width = tickRect.width + trackBuffer * 2; + labelRect.height = getHeightOfTallestLabel(); + } + else + { + labelRect.x = tickRect.x; + labelRect.y = tickRect.y + tickRect.height; + labelRect.width = tickRect.width; + labelRect.height = 0; + } + } + else + { + if (slider.getPaintLabels()) + { + labelRect.x = tickRect.x + tickRect.width; + labelRect.y = tickRect.y - trackBuffer; + labelRect.width = getWidthOfWidestLabel(); + labelRect.height = tickRect.height + trackBuffer * 2; + } + else + { + labelRect.x = tickRect.x + tickRect.width; + labelRect.y = tickRect.y; + labelRect.width = 0; + labelRect.height = tickRect.height; + } + } + } + + /** + * This method returns the width of the widest label in the slider's label + * table. + * + * @return The width of the widest label or 0 if no label table exists. + */ + protected int getWidthOfWidestLabel() + { + int widest = 0; + Dictionary table = slider.getLabelTable(); + if (table != null) + { + for (Enumeration list = slider.getLabelTable().elements(); + list.hasMoreElements();) + { + Component label = (Component) list.nextElement(); + widest = Math.max(label.getPreferredSize().width, widest); + } + } + return widest; + } + + /** + * This method returns the height of the tallest label in the slider's label + * table. + * + * @return The height of the tallest label or 0 if no label table exists. + */ + protected int getHeightOfTallestLabel() + { + int tallest = 0; + Component label; + + if (slider.getLabelTable() == null) + return 0; + Dimension pref; + for (Enumeration list = slider.getLabelTable().elements(); + list.hasMoreElements();) + { + Object comp = list.nextElement(); + if (! (comp instanceof Component)) + continue; + label = (Component) comp; + pref = label.getPreferredSize(); + if (pref != null && pref.height > tallest) + tallest = pref.height; + } + return tallest; + } + + /** + * Returns the width of the label whose key has the highest value, or 0 if + * there are no labels. + * + * @return The width of the label whose key has the highest value. + * + * @see #getHighestValueLabel() + */ + protected int getWidthOfHighValueLabel() + { + Component highValueLabel = getHighestValueLabel(); + if (highValueLabel != null) + return highValueLabel.getPreferredSize().width; + else + return 0; + } + + /** + * Returns the width of the label whose key has the lowest value, or 0 if + * there are no labels. + * + * @return The width of the label whose key has the lowest value. + * + * @see #getLowestValueLabel() + */ + protected int getWidthOfLowValueLabel() + { + Component lowValueLabel = getLowestValueLabel(); + if (lowValueLabel != null) + return lowValueLabel.getPreferredSize().width; + else + return 0; + } + + /** + * Returns the height of the label whose key has the highest value, or 0 if + * there are no labels. + * + * @return The height of the high value label or 0 if no label table exists. + */ + protected int getHeightOfHighValueLabel() + { + Component highValueLabel = getHighestValueLabel(); + if (highValueLabel != null) + return highValueLabel.getPreferredSize().height; + else + return 0; + } + + /** + * Returns the height of the label whose key has the lowest value, or 0 if + * there are no labels. + * + * @return The height of the low value label or 0 if no label table exists. + */ + protected int getHeightOfLowValueLabel() + { + Component lowValueLabel = getLowestValueLabel(); + if (lowValueLabel != null) + return lowValueLabel.getPreferredSize().height; + else + return 0; + } + + /** + * Returns <code>true</code> if the slider scale is to be drawn inverted, + * and <code>false</code> if not. + * + * @return <code>true</code> if the slider is to be drawn inverted. + */ + protected boolean drawInverted() + { + return slider.getInverted(); + } + + /** + * This method returns the label whose key has the lowest value. + * + * @return The low value label or null if no label table exists. + */ + protected Component getLowestValueLabel() + { + Integer key = new Integer(Integer.MAX_VALUE); + Integer tmpKey; + Dictionary labelTable = slider.getLabelTable(); + + if (labelTable == null) + return null; + + for (Enumeration list = labelTable.keys(); list.hasMoreElements();) + { + Object value = list.nextElement(); + if (! (value instanceof Integer)) + continue; + tmpKey = (Integer) value; + if (tmpKey.intValue() < key.intValue()) + key = tmpKey; + } + Object comp = labelTable.get(key); + if (! (comp instanceof Component)) + return null; + return (Component) comp; + } + + /** + * Returns the label whose key has the highest value. + * + * @return The label whose key has the highest value or <code>null</code> if + * no label table exists. + */ + protected Component getHighestValueLabel() + { + Integer key = new Integer(Integer.MIN_VALUE); + Integer tmpKey; + Dictionary labelTable = slider.getLabelTable(); + + if (labelTable == null) + return null; + + for (Enumeration list = labelTable.keys(); list.hasMoreElements();) + { + Object value = list.nextElement(); + if (! (value instanceof Integer)) + continue; + tmpKey = (Integer) value; + if (tmpKey.intValue() > key.intValue()) + key = tmpKey; + } + Object comp = labelTable.get(key); + if (! (comp instanceof Component)) + return null; + return (Component) comp; + } + + /** + * This method is used to paint the {@link JSlider}. It delegates all its + * duties to the various paint methods like paintTicks(), paintTrack(), + * paintThumb(), etc. + * + * @param g The {@link Graphics} object to paint with. + * @param c The {@link JComponent} that is being painted. + */ + public void paint(Graphics g, JComponent c) + { + recalculateIfInsetsChanged(); + recalculateIfOrientationChanged(); + if (slider.getPaintTrack() && hitClip(g, trackRect)) + paintTrack(g); + if (slider.getPaintTicks() && hitClip(g, tickRect)) + paintTicks(g); + if (slider.getPaintLabels() && hitClip(g, labelRect)) + paintLabels(g); + if (slider.hasFocus() && hitClip(g, focusRect)) + paintFocus(g); + if (hitClip(g, thumbRect)) + paintThumb(g); + } + + /** + * This method recalculates any rectangles that need to be recalculated + * after the insets of the component have changed. + */ + protected void recalculateIfInsetsChanged() + { + Insets insets = slider.getInsets(); + if (! insets.equals(insetCache)) + { + insetCache = insets; + calculateGeometry(); + } + } + + /** + * This method recalculates any rectangles that need to be recalculated + * after the orientation of the slider changes. + */ + protected void recalculateIfOrientationChanged() + { + // Examining a test program shows that either Sun calls private + // methods that we don't know about, or these don't do anything. + calculateThumbSize(); + calculateTrackBuffer(); + calculateTrackRect(); + calculateThumbLocation(); + + calculateTickRect(); + calculateLabelRect(); + } + + /** + * This method is called during a repaint if the slider has focus. It draws + * an outline of the focusRect using the color returned by + * getFocusColor(). + * + * @param g The {@link Graphics} object to draw with. + */ + public void paintFocus(Graphics g) + { + Color saved_color = g.getColor(); + + g.setColor(getFocusColor()); + + g.drawRect(focusRect.x, focusRect.y, focusRect.width, focusRect.height); + + g.setColor(saved_color); + } + + /** + * <p> + * This method is called during a repaint if the track is to be drawn. It + * draws a 3D rectangle to represent the track. The track is not the size + * of the trackRect. The top and left edges of the track should be outlined + * with the shadow color. The bottom and right edges should be outlined + * with the highlight color. + * </p> + * <pre> + * a---d + * | | + * | | a------------------------d + * | | | | + * | | b------------------------c + * | | + * | | + * b---c + * </pre> + * + * <p> + * The b-a-d path needs to be drawn with the shadow color and the b-c-d path + * needs to be drawn with the highlight color. + * </p> + * + * @param g The {@link Graphics} object to draw with. + */ + public void paintTrack(Graphics g) + { + Color saved_color = g.getColor(); + int width; + int height; + + Point a = new Point(trackRect.x, trackRect.y + 1); + Point b = new Point(a); + Point c = new Point(a); + Point d = new Point(a); + + if (slider.getOrientation() == JSlider.HORIZONTAL) + { + width = trackRect.width; + height = (thumbRect.height / 4 == 0) ? 1 : thumbRect.height / 4; + + a.translate(0, (trackRect.height / 2) - (height / 2)); + b.translate(0, (trackRect.height / 2) + (height / 2)); + c.translate(trackRect.width, (trackRect.height / 2) + (height / 2)); + d.translate(trackRect.width, (trackRect.height / 2) - (height / 2)); + } + else + { + width = (thumbRect.width / 4 == 0) ? 1 : thumbRect.width / 4; + height = trackRect.height; + + a.translate((trackRect.width / 2) - (width / 2), 0); + b.translate((trackRect.width / 2) - (width / 2), trackRect.height); + c.translate((trackRect.width / 2) + (width / 2), trackRect.height); + d.translate((trackRect.width / 2) + (width / 2), 0); + } + g.setColor(Color.GRAY); + g.fillRect(a.x, a.y, width, height); + + g.setColor(getHighlightColor()); + g.drawLine(b.x, b.y, c.x, c.y); + g.drawLine(c.x, c.y, d.x, d.y); + + g.setColor(getShadowColor()); + g.drawLine(b.x, b.y, a.x, a.y); + g.drawLine(a.x, a.y, d.x, d.y); + + g.setColor(saved_color); + } + + /** + * This method is called during a repaint if the ticks are to be drawn. This + * method must still verify that the majorTickSpacing and minorTickSpacing + * are greater than zero before drawing the ticks. + * + * @param g The {@link Graphics} object to draw with. + */ + public void paintTicks(Graphics g) + { + int max = slider.getMaximum(); + int min = slider.getMinimum(); + int majorSpace = slider.getMajorTickSpacing(); + int minorSpace = slider.getMinorTickSpacing(); + + if (majorSpace > 0) + { + if (slider.getOrientation() == JSlider.HORIZONTAL) + { + g.translate(0, tickRect.y); + for (int i = min; i <= max; i += majorSpace) + paintMajorTickForHorizSlider(g, tickRect, xPositionForValue(i)); + g.translate(0, -tickRect.y); + } + else // JSlider.VERTICAL + { + g.translate(tickRect.x, 0); + for (int i = min; i <= max; i += majorSpace) + paintMajorTickForVertSlider(g, tickRect, yPositionForValue(i)); + g.translate(-tickRect.x, 0); + } + } + if (minorSpace > 0) + { + if (slider.getOrientation() == JSlider.HORIZONTAL) + { + g.translate(0, tickRect.y); + for (int i = min; i <= max; i += minorSpace) + paintMinorTickForHorizSlider(g, tickRect, xPositionForValue(i)); + g.translate(0, -tickRect.y); + } + else + { + g.translate(tickRect.x, 0); + for (int i = min; i <= max; i += minorSpace) + paintMinorTickForVertSlider(g, tickRect, yPositionForValue(i)); + g.translate(-tickRect.x, 0); + } + } + } + + /* Minor ticks start at 1/4 of the height (or width) of the tickRect and + extend to 1/2 of the tickRect. + + Major ticks start at 1/4 of the height and extend to 3/4. + */ + + /** + * This method paints a minor tick for a horizontal slider at the given x + * value. x represents the x coordinate to paint at. + * + * @param g The {@link Graphics} object to draw with. + * @param tickBounds The tickRect rectangle. + * @param x The x coordinate to draw the tick at. + */ + protected void paintMinorTickForHorizSlider(Graphics g, + Rectangle tickBounds, int x) + { + int y = tickRect.height / 4; + Color saved = g.getColor(); + g.setColor(Color.BLACK); + + g.drawLine(x, y, x, y + tickRect.height / 4); + g.setColor(saved); + } + + /** + * This method paints a major tick for a horizontal slider at the given x + * value. x represents the x coordinate to paint at. + * + * @param g The {@link Graphics} object to draw with. + * @param tickBounds The tickRect rectangle. + * @param x The x coordinate to draw the tick at. + */ + protected void paintMajorTickForHorizSlider(Graphics g, + Rectangle tickBounds, int x) + { + int y = tickRect.height / 4; + Color saved = g.getColor(); + g.setColor(Color.BLACK); + + g.drawLine(x, y, x, y + tickRect.height / 2); + g.setColor(saved); + } + + /** + * This method paints a minor tick for a vertical slider at the given y + * value. y represents the y coordinate to paint at. + * + * @param g The {@link Graphics} object to draw with. + * @param tickBounds The tickRect rectangle. + * @param y The y coordinate to draw the tick at. + */ + protected void paintMinorTickForVertSlider(Graphics g, Rectangle tickBounds, + int y) + { + int x = tickRect.width / 4; + Color saved = g.getColor(); + g.setColor(Color.BLACK); + + g.drawLine(x, y, x + tickRect.width / 4, y); + g.setColor(saved); + } + + /** + * This method paints a major tick for a vertical slider at the given y + * value. y represents the y coordinate to paint at. + * + * @param g The {@link Graphics} object to draw with. + * @param tickBounds The tickRect rectangle. + * @param y The y coordinate to draw the tick at. + */ + protected void paintMajorTickForVertSlider(Graphics g, Rectangle tickBounds, + int y) + { + int x = tickRect.width / 4; + Color saved = g.getColor(); + g.setColor(Color.BLACK); + + g.drawLine(x, y, x + tickRect.width / 2, y); + g.setColor(saved); + } + + /** + * This method paints all the labels from the slider's label table. This + * method must make sure that the label table is not null before painting + * the labels. Each entry in the label table is a (integer, component) + * pair. Every label is painted at the value of the integer. + * + * @param g The {@link Graphics} object to draw with. + */ + public void paintLabels(Graphics g) + { + Dictionary table = slider.getLabelTable(); + if (table != null) + { + int min = slider.getMinimum(); + int max = slider.getMaximum(); + for (Enumeration list = table.keys(); list.hasMoreElements();) + { + Integer key = (Integer) list.nextElement(); + int value = key.intValue(); + if (value >= min && value <= max) + { + Component label = (Component) table.get(key); + if (slider.getOrientation() == JSlider.HORIZONTAL) + { + g.translate(0, labelRect.y); + paintHorizontalLabel(g, value, label); + g.translate(0, -labelRect.y); + } + else + { + g.translate(labelRect.x, 0); + paintVerticalLabel(g, value, label); + g.translate(-labelRect.x, 0); + } + } + } + } + } + + /** + * This method paints the label on the horizontal slider at the value + * specified. The value is not a coordinate. It is a value within the range + * of the slider. If the value is not within the range of the slider, this + * method will do nothing. This method should not paint outside the + * boundaries of the labelRect. + * + * @param g The {@link Graphics} object to draw with. + * @param value The value to paint at. + * @param label The label to paint. + */ + protected void paintHorizontalLabel(Graphics g, int value, Component label) + { + int center = xPositionForValue(value); + int left = center - label.getPreferredSize().width / 2; + g.translate(left, 0); + label.paint(g); + g.translate(-left, 0); + } + + /** + * This method paints the label on the vertical slider at the value + * specified. The value is not a coordinate. It is a value within the range + * of the slider. If the value is not within the range of the slider, this + * method will do nothing. This method should not paint outside the + * boundaries of the labelRect. + * + * @param g The {@link Graphics} object to draw with. + * @param value The value to paint at. + * @param label The label to paint. + */ + protected void paintVerticalLabel(Graphics g, int value, Component label) + { + int center = yPositionForValue(value); + int top = center - label.getPreferredSize().height / 2; + g.translate(0, top); + label.paint(g); + g.translate(0, -top); + } + + /** + * <p> + * This method paints a thumb. There are two types of thumb: + * </p> + * <pre> + * Vertical Horizontal + * a---b a-----b + * | | | \ + * e c | c + * \ / | / + * d e-----d + * </pre> + * + * <p> + * In the case of vertical thumbs, we highlight the path b-a-e-d and shadow + * the path b-c-d. In the case of horizontal thumbs, we highlight the path + * c-b-a-e and shadow the path c-d-e. In both cases we fill the path + * a-b-c-d-e before shadows and highlights are drawn. + * </p> + * + * @param g The graphics object to paint with + */ + public void paintThumb(Graphics g) + { + Color saved_color = g.getColor(); + + Point a = new Point(thumbRect.x, thumbRect.y); + Point b = new Point(a); + Point c = new Point(a); + Point d = new Point(a); + Point e = new Point(a); + + Polygon bright; + Polygon light; // light shadow + Polygon dark; // dark shadow + Polygon all; + + // This will be in X-dimension if the slider is inverted and y if it isn't. + int turnPoint; + + if (slider.getOrientation() == JSlider.HORIZONTAL) + { + turnPoint = thumbRect.height * 3 / 4; + + b.translate(thumbRect.width - 1, 0); + c.translate(thumbRect.width - 1, turnPoint); + d.translate(thumbRect.width / 2 - 1, thumbRect.height - 1); + e.translate(0, turnPoint); + + bright = new Polygon(new int[] { b.x - 1, a.x, e.x, d.x }, + new int[] { b.y, a.y, e.y, d.y }, 4); + + dark = new Polygon(new int[] { b.x, c.x, d.x + 1 }, new int[] { b.y, + c.y - 1, + d.y }, 3); + + light = new Polygon(new int[] { b.x - 1, c.x - 1, d.x + 1 }, + new int[] { b.y + 1, c.y - 1, d.y - 1 }, 3); + + all = new Polygon( + new int[] { a.x + 1, b.x - 2, c.x - 2, d.x, e.x + 1 }, + new int[] { a.y + 1, b.y + 1, c.y - 1, d.y - 1, e.y }, + 5); + } + else + { + turnPoint = thumbRect.width * 3 / 4 - 1; + + b.translate(turnPoint, 0); + c.translate(thumbRect.width - 1, thumbRect.height / 2); + d.translate(turnPoint, thumbRect.height - 1); + e.translate(0, thumbRect.height - 1); + + bright = new Polygon(new int[] { c.x - 1, b.x, a.x, e.x }, + new int[] { c.y - 1, b.y, a.y, e.y - 1 }, 4); + + dark = new Polygon(new int[] { c.x, d.x, e.x }, new int[] { c.y, d.y, + e.y }, 3); + + light = new Polygon(new int[] { c.x - 1, d.x, e.x + 1 }, + new int[] { c.y, d.y - 1, e.y - 1 }, 3); + all = new Polygon(new int[] { a.x + 1, b.x, c.x - 2, c.x - 2, d.x, + e.x + 1 }, new int[] { a.y + 1, b.y + 1, + c.y - 1, c.y, + d.y - 2, e.y - 2 }, + 6); + } + + g.setColor(Color.WHITE); + g.drawPolyline(bright.xpoints, bright.ypoints, bright.npoints); + + g.setColor(Color.BLACK); + g.drawPolyline(dark.xpoints, dark.ypoints, dark.npoints); + + g.setColor(Color.GRAY); + g.drawPolyline(light.xpoints, light.ypoints, light.npoints); + + g.setColor(Color.LIGHT_GRAY); + g.drawPolyline(all.xpoints, all.ypoints, all.npoints); + g.fillPolygon(all); + + g.setColor(saved_color); + } + + /** + * This method sets the position of the thumbRect. + * + * @param x The new x position. + * @param y The new y position. + */ + public void setThumbLocation(int x, int y) + { + Rectangle union = new Rectangle(thumbRect); + thumbRect.setLocation(x, y); + SwingUtilities.computeUnion(thumbRect.x, thumbRect.y, thumbRect.width, + thumbRect.height, union); + slider.repaint(union); + } + + /** + * Moves the thumb one block in the direction specified (a block is 1/10th + * of the slider range). If the slider snaps to ticks, this method is + * responsible for snapping it to a tick after the thumb has been moved. + * + * @param direction the direction (positive values increment the thumb + * position by one block, zero/negative values decrement the thumb position + * by one block). + */ + public void scrollByBlock(int direction) + { + int unit = (slider.getMaximum() - slider.getMinimum()) / 10; + int moveTo = slider.getValue(); + if (direction > 0) + moveTo += unit; + else + moveTo -= unit; + + if (slider.getSnapToTicks()) + moveTo = findClosestTick(moveTo); + + slider.setValue(moveTo); + } + + /** + * Moves the thumb one unit in the specified direction. If the slider snaps + * to ticks, this method is responsible for snapping it to a tick after the + * thumb has been moved. + * + * @param direction the direction (positive values increment the thumb + * position by one, zero/negative values decrement the thumb position by + * one). + */ + public void scrollByUnit(int direction) + { + int moveTo = slider.getValue(); + if (direction > 0) + moveTo++; + else + moveTo--; + + if (slider.getSnapToTicks()) + moveTo = findClosestTick(moveTo); + + slider.setValue(moveTo); + } + + /** + * This method is called when there has been a click in the track and the + * thumb needs to be scrolled on regular intervals. This method is only + * responsible for starting the timer and not for stopping it. + * + * @param dir The direction to move in. + */ + protected void scrollDueToClickInTrack(int dir) + { + scrollTimer.stop(); + + scrollListener.setDirection(dir); + scrollListener.setScrollByBlock(true); + + scrollTimer.start(); + } + + /** + * Returns the x-coordinate (relative to the component) for the given slider + * value. This method assumes that the <code>trackRect</code> field is + * set up. + * + * @param value the slider value. + * + * @return The x-coordinate. + */ + protected int xPositionForValue(int value) + { + int min = slider.getMinimum(); + int max = slider.getMaximum(); + int len = trackRect.width; + double range = max - min; + double pixPerVal = len / range; + int left = trackRect.x; + int right = left + trackRect.width - 1; + int xpos; + if (! drawInverted()) + xpos = left + (int) Math.round(pixPerVal * ((double) value - min)); + else + xpos = right - (int) Math.round(pixPerVal * ((double) value - min)); + xpos = Math.max(left, xpos); + xpos = Math.min(right, xpos); + return xpos; + } + + /** + * Returns the y-coordinate (relative to the component) for the given slider + * value. This method assumes that the <code>trackRect</code> field is + * set up. + * + * @param value the slider value. + * + * @return The y-coordinate. + */ + protected int yPositionForValue(int value) + { + int min = slider.getMinimum(); + int max = slider.getMaximum(); + int len = trackRect.height; + double range = max - min; + double pixPerVal = len / range; + int top = trackRect.y; + int bottom = top + trackRect.height - 1; + int ypos; + if (! drawInverted()) + ypos = top + (int) Math.round(pixPerVal * ((double) max - value)); + else + ypos = top + (int) Math.round(pixPerVal * ((double) value - min)); + ypos = Math.max(top, ypos); + ypos = Math.min(bottom, ypos); + return ypos; + } + + /** + * This method returns the value in the slider's range given the y + * coordinate. If the value is out of range, it will return the closest + * legal value. + * + * @param yPos The y coordinate to calculate a value for. + * + * @return The value for the y coordinate. + */ + public int valueForYPosition(int yPos) + { + int min = slider.getMinimum(); + int max = slider.getMaximum(); + int len = trackRect.height; + + int value; + + // If the length is 0, you shouldn't be able to even see where the slider + // is. This really shouldn't ever happen, but just in case, we'll return + // the middle. + if (len == 0) + return (max - min) / 2; + + if (! drawInverted()) + value = (len - (yPos - trackRect.y)) * (max - min) / len + min; + else + value = (yPos - trackRect.y) * (max - min) / len + min; + + // If this isn't a legal value, then we'll have to move to one now. + if (value > max) + value = max; + else if (value < min) + value = min; + return value; + } + + /** + * This method returns the value in the slider's range given the x + * coordinate. If the value is out of range, it will return the closest + * legal value. + * + * @param xPos The x coordinate to calculate a value for. + * + * @return The value for the x coordinate. + */ + public int valueForXPosition(int xPos) + { + int min = slider.getMinimum(); + int max = slider.getMaximum(); + int len = trackRect.width; + + int value; + + // If the length is 0, you shouldn't be able to even see where the slider + // is. This really shouldn't ever happen, but just in case, we'll return + // the middle. + if (len == 0) + return (max - min) / 2; + + if (! drawInverted()) + value = (xPos - trackRect.x) * (max - min) / len + min; + else + value = (len - (xPos - trackRect.x)) * (max - min) / len + min; + + // If this isn't a legal value, then we'll have to move to one now. + if (value > max) + value = max; + else if (value < min) + value = min; + return value; + } + + /** + * This method finds the closest value that has a tick associated with it. + * This is package-private to avoid an accessor method. + * + * @param value The value to search from. + * + * @return The closest value that has a tick associated with it. + */ + int findClosestTick(int value) + { + int min = slider.getMinimum(); + int max = slider.getMaximum(); + int majorSpace = slider.getMajorTickSpacing(); + int minorSpace = slider.getMinorTickSpacing(); + + // The default value to return is value + minor or + // value + major. + // Initializing at min - value leaves us with a default + // return value of min, which always has tick marks + // (if ticks are painted). + int minor = min - value; + int major = min - value; + + // If there are no major tick marks or minor tick marks + // e.g. snap is set to true but no ticks are set, then + // we can just return the value. + if (majorSpace <= 0 && minorSpace <= 0) + return value; + + // First check the major ticks. + if (majorSpace > 0) + { + int lowerBound = (value - min) / majorSpace; + int majLower = majorSpace * lowerBound + min; + int majHigher = majorSpace * (lowerBound + 1) + min; + + if (majHigher <= max && majHigher - value <= value - majLower) + major = majHigher - value; + else + major = majLower - value; + } + + if (minorSpace > 0) + { + int lowerBound = value / minorSpace; + int minLower = minorSpace * lowerBound; + int minHigher = minorSpace * (lowerBound + 1); + + if (minHigher <= max && minHigher - value <= value - minLower) + minor = minHigher - value; + else + minor = minLower - value; + } + + // Give preference to minor ticks + if (Math.abs(minor) > Math.abs(major)) + return value + major; + else + return value + minor; + } + + InputMap getInputMap(int condition) + { + if (condition == JComponent.WHEN_FOCUSED) + return (InputMap) UIManager.get("Slider.focusInputMap"); + return null; + } + + /** + * Returns the action map for the {@link JSlider}. All sliders share + * a single action map which is created the first time this method is + * called, then stored in the UIDefaults table for subsequent access. + * + * @return The shared action map. + */ + ActionMap getActionMap() + { + ActionMap map = (ActionMap) UIManager.get("Slider.actionMap"); + + if (map == null) // first time here + { + map = createActionMap(); + if (map != null) + UIManager.put("Slider.actionMap", map); + } + return map; + } + + /** + * Creates the action map shared by all {@link JSlider} instances. + * This method is called once by {@link #getActionMap()} when it + * finds no action map in the UIDefaults table...after the map is + * created, it gets added to the defaults table so that subsequent + * calls to {@link #getActionMap()} will return the same shared + * instance. + * + * @return The action map. + */ + ActionMap createActionMap() + { + ActionMap map = new ActionMapUIResource(); + map.put("positiveUnitIncrement", + new AbstractAction("positiveUnitIncrement") { + public void actionPerformed(ActionEvent event) + { + JSlider slider = (JSlider) event.getSource(); + BasicSliderUI ui = (BasicSliderUI) slider.getUI(); + if (slider.getInverted()) + ui.scrollByUnit(BasicSliderUI.NEGATIVE_SCROLL); + else + ui.scrollByUnit(BasicSliderUI.POSITIVE_SCROLL); + } + } + ); + map.put("negativeUnitIncrement", + new AbstractAction("negativeUnitIncrement") { + public void actionPerformed(ActionEvent event) + { + JSlider slider = (JSlider) event.getSource(); + BasicSliderUI ui = (BasicSliderUI) slider.getUI(); + if (slider.getInverted()) + ui.scrollByUnit(BasicSliderUI.POSITIVE_SCROLL); + else + ui.scrollByUnit(BasicSliderUI.NEGATIVE_SCROLL); + } + } + ); + map.put("positiveBlockIncrement", + new AbstractAction("positiveBlockIncrement") { + public void actionPerformed(ActionEvent event) + { + JSlider slider = (JSlider) event.getSource(); + BasicSliderUI ui = (BasicSliderUI) slider.getUI(); + if (slider.getInverted()) + ui.scrollByBlock(BasicSliderUI.NEGATIVE_SCROLL); + else + ui.scrollByBlock(BasicSliderUI.POSITIVE_SCROLL); + } + } + ); + map.put("negativeBlockIncrement", + new AbstractAction("negativeBlockIncrement") { + public void actionPerformed(ActionEvent event) + { + JSlider slider = (JSlider) event.getSource(); + BasicSliderUI ui = (BasicSliderUI) slider.getUI(); + if (slider.getInverted()) + ui.scrollByBlock(BasicSliderUI.POSITIVE_SCROLL); + else + ui.scrollByBlock(BasicSliderUI.NEGATIVE_SCROLL); + } + } + ); + map.put("minScroll", + new AbstractAction("minScroll") { + public void actionPerformed(ActionEvent event) + { + JSlider slider = (JSlider) event.getSource(); + if (slider.getInverted()) + slider.setValue(slider.getMaximum()); + else + slider.setValue(slider.getMinimum()); + } + } + ); + map.put("maxScroll", + new AbstractAction("maxScroll") { + public void actionPerformed(ActionEvent event) + { + JSlider slider = (JSlider) event.getSource(); + if (slider.getInverted()) + slider.setValue(slider.getMinimum()); + else + slider.setValue(slider.getMaximum()); + } + } + ); + return map; + } + + /** + * Small utility method to save me from typing the hell out of myself in + * paint(). + */ + private boolean hitClip(Graphics g, Rectangle r) + { + return g.hitClip(r.x, r.y, r.width, r.height); + } +} diff --git a/libjava/classpath/javax/swing/plaf/basic/BasicSpinnerUI.java b/libjava/classpath/javax/swing/plaf/basic/BasicSpinnerUI.java new file mode 100644 index 000000000..00c8537cc --- /dev/null +++ b/libjava/classpath/javax/swing/plaf/basic/BasicSpinnerUI.java @@ -0,0 +1,561 @@ +/* BasicSpinnerUI.java -- + Copyright (C) 2003, 2004, 2005, 2006, 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 java.awt.Component; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.Insets; +import java.awt.LayoutManager; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; + +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JSpinner; +import javax.swing.LookAndFeel; +import javax.swing.Timer; +import javax.swing.plaf.ComponentUI; +import javax.swing.plaf.SpinnerUI; + +/** + * A UI delegate for the {@link JSpinner} component. + * + * @author Ka-Hing Cheung + * + * @since 1.4 + */ +public class BasicSpinnerUI extends SpinnerUI +{ + /** + * Creates a new <code>BasicSpinnerUI</code> for the specified + * <code>JComponent</code> + * + * @param c the component (ignored). + * + * @return A new instance of {@link BasicSpinnerUI}. + */ + public static ComponentUI createUI(JComponent c) + { + return new BasicSpinnerUI(); + } + + /** + * Creates an editor component. Really, it just returns + * <code>JSpinner.getEditor()</code> + * + * @return a JComponent as an editor + * + * @see javax.swing.JSpinner#getEditor + */ + protected JComponent createEditor() + { + return spinner.getEditor(); + } + + /** + * Creates a <code>LayoutManager</code> that layouts the sub components. The + * subcomponents are identifies by the constraint "Next", "Previous" and + * "Editor" + * + * @return a LayoutManager + * + * @see java.awt.LayoutManager + */ + protected LayoutManager createLayout() + { + return new DefaultLayoutManager(); + } + + /** + * Creates the "Next" button + * + * @return the next button component + */ + protected Component createNextButton() + { + JButton button = new BasicArrowButton(BasicArrowButton.NORTH); + return button; + } + + /** + * Creates the "Previous" button + * + * @return the previous button component + */ + protected Component createPreviousButton() + { + JButton button = new BasicArrowButton(BasicArrowButton.SOUTH); + return button; + } + + /** + * Creates the <code>PropertyChangeListener</code> that will be attached by + * <code>installListeners</code>. It should watch for the "editor" + * property, when it's changed, replace the old editor with the new one, + * probably by calling <code>replaceEditor</code> + * + * @return a PropertyChangeListener + * + * @see #replaceEditor + */ + protected PropertyChangeListener createPropertyChangeListener() + { + return new PropertyChangeListener() + { + public void propertyChange(PropertyChangeEvent event) + { + // FIXME: Add check for enabled property change. Need to + // disable the buttons. + if ("editor".equals(event.getPropertyName())) + BasicSpinnerUI.this.replaceEditor((JComponent) event.getOldValue(), + (JComponent) event.getNewValue()); + // FIXME: Handle 'font' property change + } + }; + } + + /** + * Called by <code>installUI</code>. This should set various defaults + * obtained from <code>UIManager.getLookAndFeelDefaults</code>, as well as + * set the layout obtained from <code>createLayout</code> + * + * @see javax.swing.UIManager#getLookAndFeelDefaults + * @see #createLayout + * @see #installUI + */ + protected void installDefaults() + { + LookAndFeel.installColorsAndFont(spinner, "Spinner.background", + "Spinner.foreground", "Spinner.font"); + LookAndFeel.installBorder(spinner, "Spinner.border"); + JComponent e = spinner.getEditor(); + if (e instanceof JSpinner.DefaultEditor) + { + JSpinner.DefaultEditor de = (JSpinner.DefaultEditor) e; + de.getTextField().setBorder(null); + } + spinner.setLayout(createLayout()); + spinner.setOpaque(true); + } + + /* + * Called by <code>installUI</code>, which basically adds the + * <code>PropertyChangeListener</code> created by + * <code>createPropertyChangeListener</code> + * + * @see #createPropertyChangeListener + * @see #installUI + */ + protected void installListeners() + { + spinner.addPropertyChangeListener(listener); + } + + /* + * Install listeners to the next button so that it increments the model + */ + protected void installNextButtonListeners(Component c) + { + c.addMouseListener(new MouseAdapter() + { + public void mousePressed(MouseEvent evt) + { + if (! spinner.isEnabled()) + return; + increment(); + timer.setInitialDelay(500); + timer.start(); + } + + public void mouseReleased(MouseEvent evt) + { + timer.stop(); + } + + void increment() + { + Object next = BasicSpinnerUI.this.spinner.getNextValue(); + if (next != null) + BasicSpinnerUI.this.spinner.getModel().setValue(next); + } + + volatile boolean mouseDown; + Timer timer = new Timer(50, + new ActionListener() + { + public void actionPerformed(ActionEvent event) + { + increment(); + } + }); + }); + } + + /* + * Install listeners to the previous button so that it decrements the model + */ + protected void installPreviousButtonListeners(Component c) + { + c.addMouseListener(new MouseAdapter() + { + public void mousePressed(MouseEvent evt) + { + if (! spinner.isEnabled()) + return; + decrement(); + timer.setInitialDelay(500); + timer.start(); + } + + public void mouseReleased(MouseEvent evt) + { + timer.stop(); + } + + void decrement() + { + Object prev = BasicSpinnerUI.this.spinner.getPreviousValue(); + if (prev != null) + BasicSpinnerUI.this.spinner.getModel().setValue(prev); + } + + volatile boolean mouseDown; + Timer timer = new Timer(50, + new ActionListener() + { + public void actionPerformed(ActionEvent event) + { + decrement(); + } + }); + }); + } + + /** + * Install this UI to the <code>JComponent</code>, which in reality, is a + * <code>JSpinner</code>. Calls <code>installDefaults</code>, + * <code>installListeners</code>, and also adds the buttons and editor. + * + * @param c DOCUMENT ME! + * + * @see #installDefaults + * @see #installListeners + * @see #createNextButton + * @see #createPreviousButton + * @see #createEditor + */ + public void installUI(JComponent c) + { + super.installUI(c); + + spinner = (JSpinner) c; + + installDefaults(); + installListeners(); + + Component next = createNextButton(); + Component previous = createPreviousButton(); + + installNextButtonListeners(next); + installPreviousButtonListeners(previous); + + c.add(createEditor(), "Editor"); + c.add(next, "Next"); + c.add(previous, "Previous"); + } + + /** + * Replace the old editor with the new one + * + * @param oldEditor the old editor + * @param newEditor the new one to replace with + */ + protected void replaceEditor(JComponent oldEditor, JComponent newEditor) + { + spinner.remove(oldEditor); + spinner.add(newEditor); + } + + /** + * The reverse of <code>installDefaults</code>. Called by + * <code>uninstallUI</code> + */ + protected void uninstallDefaults() + { + spinner.setLayout(null); + } + + /** + * The reverse of <code>installListeners</code>, called by + * <code>uninstallUI</code> + */ + protected void uninstallListeners() + { + spinner.removePropertyChangeListener(listener); + } + + /** + * Called when the current L&F is replaced with another one, should call + * <code>uninstallDefaults</code> and <code>uninstallListeners</code> as + * well as remove the next/previous buttons and the editor + * + * @param c DOCUMENT ME! + */ + public void uninstallUI(JComponent c) + { + super.uninstallUI(c); + + uninstallDefaults(); + uninstallListeners(); + c.removeAll(); + } + + /** The spinner for this UI */ + protected JSpinner spinner; + + /** DOCUMENT ME! */ + private PropertyChangeListener listener = createPropertyChangeListener(); + + /** + * A layout manager for the {@link JSpinner} component. The spinner has + * three subcomponents: an editor, a 'next' button and a 'previous' button. + */ + private class DefaultLayoutManager implements LayoutManager + { + /** + * Layout the spinners inner parts. + * + * @param parent The parent container + */ + public void layoutContainer(Container parent) + { + synchronized (parent.getTreeLock()) + { + Insets i = parent.getInsets(); + boolean l2r = parent.getComponentOrientation().isLeftToRight(); + /* + -------------- -------------- + | | n | | n | | + | e | - | or | - | e | + | | p | | p | | + -------------- -------------- + */ + Dimension e = prefSize(editor); + Dimension n = prefSize(next); + Dimension p = prefSize(previous); + Dimension s = parent.getSize(); + + int x = l2r ? i.left : i.right; + int y = i.top; + int w = Math.max(p.width, n.width); + int h = (s.height - i.bottom) / 2; + int e_width = s.width - w - i.left - i.right; + + if (l2r) + { + setBounds(editor, x, y, e_width, 2 * h); + x += e_width; + setBounds(next, x, y, w, h); + y += h; + setBounds(previous, x, y, w, h); + } + else + { + setBounds(next, x, y + (s.height - e.height) / 2, w, h); + y += h; + setBounds(previous, x, y + (s.height - e.height) / 2, w, h); + x += w; + y -= h; + setBounds(editor, x, y, e_width, e.height); + } + } + } + + /** + * Calculates the minimum layout size. + * + * @param parent the parent. + * + * @return The minimum layout size. + */ + public Dimension minimumLayoutSize(Container parent) + { + Dimension d = new Dimension(); + + if (editor != null) + { + Dimension tmp = editor.getMinimumSize(); + d.width += tmp.width; + d.height = tmp.height; + } + + int nextWidth = 0; + int previousWidth = 0; + + if (next != null) + { + Dimension tmp = next.getMinimumSize(); + nextWidth = tmp.width; + } + if (previous != null) + { + Dimension tmp = previous.getMinimumSize(); + previousWidth = tmp.width; + } + + d.width += Math.max(nextWidth, previousWidth); + + return d; + } + + /** + * Returns the preferred layout size of the container. + * + * @param parent DOCUMENT ME! + * + * @return DOCUMENT ME! + */ + public Dimension preferredLayoutSize(Container parent) + { + Dimension d = new Dimension(); + + if (editor != null) + { + Dimension tmp = editor.getPreferredSize(); + d.width += Math.max(tmp.width, 40); + d.height = tmp.height; + } + + int nextWidth = 0; + int previousWidth = 0; + + if (next != null) + { + Dimension tmp = next.getPreferredSize(); + nextWidth = tmp.width; + } + if (previous != null) + { + Dimension tmp = previous.getPreferredSize(); + previousWidth = tmp.width; + } + + d.width += Math.max(nextWidth, previousWidth); + Insets insets = parent.getInsets(); + d.width = d.width + insets.left + insets.right; + d.height = d.height + insets.top + insets.bottom; + return d; + } + + /** + * DOCUMENT ME! + * + * @param child DOCUMENT ME! + */ + public void removeLayoutComponent(Component child) + { + if (child == editor) + editor = null; + else if (child == next) + next = null; + else if (previous == child) + previous = null; + } + + /** + * DOCUMENT ME! + * + * @param name DOCUMENT ME! + * @param child DOCUMENT ME! + */ + public void addLayoutComponent(String name, Component child) + { + if ("Editor".equals(name)) + editor = child; + else if ("Next".equals(name)) + next = child; + else if ("Previous".equals(name)) + previous = child; + } + + /** + * DOCUMENT ME! + * + * @param c DOCUMENT ME! + * + * @return DOCUMENT ME! + */ + private Dimension prefSize(Component c) + { + if (c == null) + return new Dimension(); + else + return c.getPreferredSize(); + } + + /** + * Sets the bounds for the specified component. + * + * @param c the component. + * @param x the x-coordinate for the top-left of the component bounds. + * @param y the y-coordinate for the top-left of the component bounds. + * @param w the width of the bounds. + * @param h the height of the bounds. + */ + private void setBounds(Component c, int x, int y, int w, int h) + { + if (c != null) + c.setBounds(x, y, w, h); + } + + /** The editor component. */ + private Component editor; + + /** The next button. */ + private Component next; + + /** The previous button. */ + private Component previous; + } +} diff --git a/libjava/classpath/javax/swing/plaf/basic/BasicSplitPaneDivider.java b/libjava/classpath/javax/swing/plaf/basic/BasicSplitPaneDivider.java new file mode 100644 index 000000000..53f7db6e7 --- /dev/null +++ b/libjava/classpath/javax/swing/plaf/basic/BasicSplitPaneDivider.java @@ -0,0 +1,1077 @@ +/* BasicSplitPaneDivider.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 java.awt.Color; +import java.awt.Component; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Insets; +import java.awt.LayoutManager; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.event.MouseMotionListener; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; + +import javax.swing.JButton; +import javax.swing.JSplitPane; +import javax.swing.UIManager; +import javax.swing.border.Border; + +/** + * The divider that separates the two parts of a JSplitPane in the Basic look + * and feel. + * + * <p> + * Implementation status: We do not have a real implementation yet. Currently, + * it is mostly a stub to allow compiling other parts of the + * javax.swing.plaf.basic package, although some parts are already + * functional. + * </p> + * + * @author Sascha Brawer (brawer_AT_dandelis.ch) + */ +public class BasicSplitPaneDivider extends Container + implements PropertyChangeListener +{ + /** + * The buttons used as one touch buttons. + */ + private class BasicOneTouchButton + extends JButton + { + /** + * Denotes a left button. + */ + static final int LEFT = 0; + + /** + * Denotes a right button. + */ + static final int RIGHT = 1; + + /** + * The x points for the arrow. + */ + private int[] xpoints; + + /** + * The y points for the arrow. + */ + private int[] ypoints; + + /** + * Either LEFT or RIGHT. + */ + private int direction; + + /** + * Creates a new instance. + * + * @param dir either LEFT or RIGHT + */ + BasicOneTouchButton(int dir) + { + direction = dir; + xpoints = new int[3]; + ypoints = new int[3]; + } + + /** + * Never allow borders. + */ + public void setBorder(Border b) + { + } + + /** + * Never allow focus traversal. + */ + public boolean isFocusTraversable() + { + return false; + } + + /** + * Paints the one touch button. + */ + public void paint(Graphics g) + { + if (splitPane != null) + { + // Fill background. + g.setColor(splitPane.getBackground()); + g.fillRect(0, 0, getWidth(), getHeight()); + + // Draw arrow. + int size; + if (direction == LEFT) + { + if (orientation == JSplitPane.VERTICAL_SPLIT) + { + size = Math.min(getHeight(), ONE_TOUCH_SIZE); + xpoints[0] = 0; + xpoints[1] = size / 2; + xpoints[2] = size; + ypoints[0] = size; + ypoints[1] = 0; + ypoints[2] = size; + } + else + { + size = Math.min(getWidth(), ONE_TOUCH_SIZE); + xpoints[0] = size; + xpoints[1] = 0; + xpoints[2] = size; + ypoints[0] = 0; + ypoints[1] = size / 2; + ypoints[2] = size; + } + } + else + { + if (orientation == JSplitPane.VERTICAL_SPLIT) + { + size = Math.min(getHeight(), ONE_TOUCH_SIZE); + xpoints[0] = 0; + xpoints[1] = size / 2; + xpoints[2] = size; + ypoints[0] = 0; + ypoints[1] = size; + ypoints[2] = 0; + } + else + { + size = Math.min(getWidth(), ONE_TOUCH_SIZE); + xpoints[0] = 0; + xpoints[1] = size; + xpoints[2] = 0; + ypoints[0] = 0; + ypoints[1] = size / 2; + ypoints[2] = size; + } + } + g.setColor(Color.BLACK); + g.fillPolygon(xpoints, ypoints, 3); + } + } + } + + /** + * Listens for actions on the one touch buttons. + */ + private class OneTouchAction + implements ActionListener + { + + public void actionPerformed(ActionEvent ev) + { + Insets insets = splitPane.getInsets(); + int lastLoc = splitPane.getLastDividerLocation(); + int currentLoc = splitPaneUI.getDividerLocation(splitPane); + int newLoc; + + if (ev.getSource() == leftButton) + { + if (orientation == JSplitPane.VERTICAL_SPLIT) + { + if (currentLoc + >= splitPane.getHeight() - insets.bottom - getHeight()) + { + newLoc = Math.min(splitPane.getMaximumDividerLocation(), + lastLoc); + } + else + { + newLoc = insets.top; + } + } + else + { + if (currentLoc + >= splitPane.getWidth() - insets.right - getWidth()) + { + newLoc = Math.min(splitPane.getMaximumDividerLocation(), + lastLoc); + } + else + { + newLoc = insets.left; + } + } + } + else + { + if (orientation == JSplitPane.VERTICAL_SPLIT) + { + if (currentLoc == insets.top) + { + newLoc = Math.min(splitPane.getMaximumDividerLocation(), + lastLoc); + } + else + { + newLoc = splitPane.getHeight() - insets.top - getHeight(); + } + } + else + { + if (currentLoc == insets.left) + { + newLoc = Math.min(splitPane.getMaximumDividerLocation(), + lastLoc); + } + else + { + newLoc = splitPane.getWidth() - insets.left - getWidth(); + } + } + } + if (currentLoc != newLoc) + { + splitPane.setDividerLocation(newLoc); + splitPane.setLastDividerLocation(currentLoc); + } + } + } + + /** + * Determined using the <code>serialver</code> tool of Apple/Sun JDK 1.3.1 + * on MacOS X 10.1.5. + */ + static final long serialVersionUID = 1463404307042803342L; + + /** + * The width and height of the little buttons for showing and hiding parts + * of a JSplitPane in a single mouse click. + */ + protected static final int ONE_TOUCH_SIZE = 6; + + /** The distance the one touch buttons will sit from the divider's edges. */ + protected static final int ONE_TOUCH_OFFSET = 2; + + /** + * An object that performs the tasks associated with an ongoing drag + * operation, or <code>null</code> if the user is currently not dragging + * the divider. + */ + protected DragController dragger; + + /** + * The delegate object that is responsible for the UI of the + * <code>JSplitPane</code> that contains this divider. + */ + protected BasicSplitPaneUI splitPaneUI; + + /** The thickness of the divider in pixels. */ + protected int dividerSize; + + /** A divider that is used for layout purposes. */ + protected Component hiddenDivider; + + /** The JSplitPane containing this divider. */ + protected JSplitPane splitPane; + + /** + * The listener for handling mouse events from both the divider and the + * containing <code>JSplitPane</code>. + * + * <p> + * The reason for also handling MouseEvents from the containing + * <code>JSplitPane</code> is that users should be able to start a drag + * gesture from inside the JSplitPane, but slightly outisde the divider. + * </p> + */ + protected MouseHandler mouseHandler = new MouseHandler(); + + /** + * The current orientation of the containing <code>JSplitPane</code>, which + * is either {@link javax.swing.JSplitPane#HORIZONTAL_SPLIT} or {@link + * javax.swing.JSplitPane#VERTICAL_SPLIT}. + */ + protected int orientation; + + /** + * The button for showing and hiding the left (or top) component of the + * <code>JSplitPane</code>. + */ + protected JButton leftButton; + + /** + * The button for showing and hiding the right (or bottom) component of the + * <code>JSplitPane</code>. + */ + protected JButton rightButton; + + /** + * The border of this divider. Typically, this will be an instance of {@link + * javax.swing.plaf.basic.BasicBorders.SplitPaneDividerBorder}. + * + * @see #getBorder() + * @see #setBorder(javax.swing.border.Border) + */ + private Border border; + + // This is not a pixel count. + // This int should be able to take 3 values. + // left (top), middle, right(bottom) + // 0 1 2 + + /** + * Keeps track of where the divider should be placed when using one touch + * expand buttons. + * This is package-private to avoid an accessor method. + */ + transient int currentDividerLocation = 1; + + /** + * Indicates if the ont touch buttons are laid out centered or at the + * top/left. + * + * Package private to avoid accessor method. + */ + boolean centerOneTouchButtons; + + /** + * Constructs a new divider. + * + * @param ui the UI delegate of the enclosing <code>JSplitPane</code>. + */ + public BasicSplitPaneDivider(BasicSplitPaneUI ui) + { + setLayout(new DividerLayout()); + setBasicSplitPaneUI(ui); + setDividerSize(splitPane.getDividerSize()); + centerOneTouchButtons = + UIManager.getBoolean("SplitPane.centerOneTouchButtons"); + } + + /** + * Sets the delegate object that is responsible for the UI of the {@link + * javax.swing.JSplitPane} containing this divider. + * + * @param newUI the UI delegate, or <code>null</code> to release the + * connection to the current delegate. + */ + public void setBasicSplitPaneUI(BasicSplitPaneUI newUI) + { + /* Remove the connection to the existing JSplitPane. */ + if (splitPane != null) + { + splitPane.removePropertyChangeListener(this); + removeMouseListener(mouseHandler); + removeMouseMotionListener(mouseHandler); + splitPane = null; + hiddenDivider = null; + } + + /* Establish the connection to the new JSplitPane. */ + splitPaneUI = newUI; + if (splitPaneUI != null) + splitPane = newUI.getSplitPane(); + if (splitPane != null) + { + splitPane.addPropertyChangeListener(this); + addMouseListener(mouseHandler); + addMouseMotionListener(mouseHandler); + hiddenDivider = splitPaneUI.getNonContinuousLayoutDivider(); + orientation = splitPane.getOrientation(); + if (splitPane.isOneTouchExpandable()) + oneTouchExpandableChanged(); + } + } + + /** + * Returns the delegate object that is responsible for the UI of the {@link + * javax.swing.JSplitPane} containing this divider. + * + * @return The UI for the JSplitPane. + */ + public BasicSplitPaneUI getBasicSplitPaneUI() + { + return splitPaneUI; + } + + /** + * Sets the thickness of the divider. + * + * @param newSize the new width or height in pixels. + */ + public void setDividerSize(int newSize) + { + this.dividerSize = newSize; + } + + /** + * Retrieves the thickness of the divider. + * + * @return The thickness of the divider. + */ + public int getDividerSize() + { + return dividerSize; + } + + /** + * Sets the border of this divider. + * + * @param border the new border. Typically, this will be an instance of + * {@link + * javax.swing.plaf.basic.BasicBorders.SplitPaneBorder}. + * + * @since 1.3 + */ + public void setBorder(Border border) + { + if (border != this.border) + { + Border oldValue = this.border; + this.border = border; + firePropertyChange("border", oldValue, border); + } + } + + /** + * Retrieves the border of this divider. + * + * @return the current border, or <code>null</code> if no border has been + * set. + * + * @since 1.3 + */ + public Border getBorder() + { + return border; + } + + /** + * Retrieves the insets of the divider. If a border has been installed on + * the divider, the result of calling its <code>getBorderInsets</code> + * method is returned. Otherwise, the inherited implementation will be + * invoked. + * + * @see javax.swing.border.Border#getBorderInsets(java.awt.Component) + */ + public Insets getInsets() + { + if (border != null) + return border.getBorderInsets(this); + else + return super.getInsets(); + } + + /** + * Returns the preferred size of this divider, which is + * <code>dividerSize</code> by <code>dividerSize</code> pixels. + * + * @return The preferred size of the divider. + */ + public Dimension getPreferredSize() + { + Dimension d; + if (orientation == JSplitPane.HORIZONTAL_SPLIT) + d = new Dimension(getDividerSize(), 1); + else + d = new Dimension(1, getDividerSize()); + return d; + } + + /** + * Returns the minimal size of this divider, which is + * <code>dividerSize</code> by <code>dividerSize</code> pixels. + * + * @return The minimal size of the divider. + */ + public Dimension getMinimumSize() + { + return getPreferredSize(); + } + + /** + * Processes events from the <code>JSplitPane</code> that contains this + * divider. + * + * @param e The PropertyChangeEvent. + */ + public void propertyChange(PropertyChangeEvent e) + { + if (e.getPropertyName().equals(JSplitPane.ONE_TOUCH_EXPANDABLE_PROPERTY)) + oneTouchExpandableChanged(); + else if (e.getPropertyName().equals(JSplitPane.ORIENTATION_PROPERTY)) + { + orientation = splitPane.getOrientation(); + invalidate(); + if (splitPane != null) + splitPane.revalidate(); + } + else if (e.getPropertyName().equals(JSplitPane.DIVIDER_SIZE_PROPERTY)) + dividerSize = splitPane.getDividerSize(); + } + + /** + * Paints the divider by painting its border. + * + * @param g The Graphics Object to paint with. + */ + public void paint(Graphics g) + { + Dimension dividerSize; + + super.paint(g); + if (border != null) + { + dividerSize = getSize(); + border.paintBorder(this, g, 0, 0, dividerSize.width, dividerSize.height); + } + } + + /** + * Reacts to changes of the <code>oneToughExpandable</code> property of the + * containing <code>JSplitPane</code>. + */ + protected void oneTouchExpandableChanged() + { + if (splitPane.isOneTouchExpandable()) + { + leftButton = createLeftOneTouchButton(); + if (leftButton != null) + leftButton.addActionListener(new OneTouchAction()); + + rightButton = createRightOneTouchButton(); + if (rightButton != null) + rightButton.addActionListener(new OneTouchAction()); + + // Only add them when both are non-null. + if (leftButton != null && rightButton != null) + { + add(leftButton); + add(rightButton); + } + } + invalidate(); + if (splitPane != null) + splitPane.revalidate(); + } + + /** + * Creates a button for showing and hiding the left (or top) part of a + * <code>JSplitPane</code>. + * + * @return The left one touch button. + */ + protected JButton createLeftOneTouchButton() + { + JButton button = new BasicOneTouchButton(BasicOneTouchButton.LEFT); + button.setMinimumSize(new Dimension(ONE_TOUCH_SIZE, ONE_TOUCH_SIZE)); + button.setRequestFocusEnabled(false); + return button; + } + + /** + * Creates a button for showing and hiding the right (or bottom) part of a + * <code>JSplitPane</code>. + * + * @return The right one touch button. + */ + protected JButton createRightOneTouchButton() + { + JButton button = new BasicOneTouchButton(BasicOneTouchButton.RIGHT); + button.setMinimumSize(new Dimension(ONE_TOUCH_SIZE, ONE_TOUCH_SIZE)); + button.setRequestFocusEnabled(false); + return button; + } + + /** + * Prepares the divider for dragging by calling the + * <code>startDragging</code> method of the UI delegate of the enclosing + * <code>JSplitPane</code>. + * + * @see BasicSplitPaneUI#startDragging() + */ + protected void prepareForDragging() + { + if (splitPaneUI != null) + splitPaneUI.startDragging(); + } + + /** + * Drags the divider to a given location by calling the + * <code>dragDividerTo</code> method of the UI delegate of the enclosing + * <code>JSplitPane</code>. + * + * @param location the new location of the divider. + * + * @see BasicSplitPaneUI#dragDividerTo(int location) + */ + protected void dragDividerTo(int location) + { + if (splitPaneUI != null) + splitPaneUI.dragDividerTo(location); + } + + /** + * Finishes a dragging gesture by calling the <code>finishDraggingTo</code> + * method of the UI delegate of the enclosing <code>JSplitPane</code>. + * + * @param location the new, final location of the divider. + * + * @see BasicSplitPaneUI#finishDraggingTo(int location) + */ + protected void finishDraggingTo(int location) + { + if (splitPaneUI != null) + splitPaneUI.finishDraggingTo(location); + } + + /** + * This helper method moves the divider to one of the three locations when + * using one touch expand buttons. Location 0 is the left (or top) most + * location. Location 1 is the middle. Location 2 is the right (or bottom) + * most location. + * This is package-private to avoid an accessor method. + * + * @param locationIndex The location to move to. + */ + void moveDividerTo(int locationIndex) + { + Insets insets = splitPane.getInsets(); + switch (locationIndex) + { + case 1: + splitPane.setDividerLocation(splitPane.getLastDividerLocation()); + break; + case 0: + int top = (orientation == JSplitPane.HORIZONTAL_SPLIT) ? insets.left + : insets.top; + splitPane.setDividerLocation(top); + break; + case 2: + int bottom; + if (orientation == JSplitPane.HORIZONTAL_SPLIT) + bottom = splitPane.getBounds().width - insets.right - dividerSize; + else + bottom = splitPane.getBounds().height - insets.bottom - dividerSize; + splitPane.setDividerLocation(bottom); + break; + } + } + + /** + * The listener for handling mouse events from both the divider and the + * containing <code>JSplitPane</code>. + * + * <p> + * The reason for also handling MouseEvents from the containing + * <code>JSplitPane</code> is that users should be able to start a drag + * gesture from inside the JSplitPane, but slightly outisde the divider. + * </p> + * + * @author Sascha Brawer (brawer_AT_dandelis.ch) + */ + protected class MouseHandler extends MouseAdapter + implements MouseMotionListener + { + /** Keeps track of whether a drag is occurring. */ + private transient boolean isDragging; + + /** + * This method is called when the mouse is pressed. + * + * @param e The MouseEvent. + */ + public void mousePressed(MouseEvent e) + { + isDragging = true; + currentDividerLocation = 1; + if (orientation == JSplitPane.HORIZONTAL_SPLIT) + dragger = new DragController(e); + else + dragger = new VerticalDragController(e); + prepareForDragging(); + } + + /** + * This method is called when the mouse is released. + * + * @param e The MouseEvent. + */ + public void mouseReleased(MouseEvent e) + { + if (isDragging) + dragger.completeDrag(e); + isDragging = false; + } + + /** + * Repeatedly invoked when the user is dragging the mouse cursor while + * having pressed a mouse button. + * + * @param e The MouseEvent. + */ + public void mouseDragged(MouseEvent e) + { + if (dragger != null) + dragger.continueDrag(e); + } + + /** + * Repeatedly invoked when the user is dragging the mouse cursor without + * having pressed a mouse button. + * + * @param e The MouseEvent. + */ + public void mouseMoved(MouseEvent e) + { + // Do nothing. + } + } + + /** + * Performs the tasks associated with an ongoing drag operation. + * + * @author Sascha Brawer (brawer_AT_dandelis.ch) + */ + protected class DragController + { + /** + * The difference between where the mouse is clicked and the initial + * divider location. + */ + transient int offset; + + /** + * Creates a new DragController object. + * + * @param e The MouseEvent to initialize with. + */ + protected DragController(MouseEvent e) + { + offset = e.getX(); + } + + /** + * This method returns true if the divider can move. + * + * @return True if dragging is allowed. + */ + protected boolean isValid() + { + // Views can always be resized? + return true; + } + + /** + * Returns a position for the divider given the MouseEvent. + * + * @param e MouseEvent. + * + * @return The position for the divider to move to. + */ + protected int positionForMouseEvent(MouseEvent e) + { + return e.getX() + getX() - offset; + } + + /** + * This method returns one of the two paramters for the orientation. In + * this case, it returns x. + * + * @param x The x coordinate. + * @param y The y coordinate. + * + * @return The x coordinate. + */ + protected int getNeededLocation(int x, int y) + { + return x; + } + + /** + * This method is called to pass on the drag information to the UI through + * dragDividerTo. + * + * @param newX The x coordinate of the MouseEvent. + * @param newY The y coordinate of the MouseEvent. + */ + protected void continueDrag(int newX, int newY) + { + if (isValid()) + dragDividerTo(adjust(newX, newY)); + } + + /** + * This method is called to pass on the drag information to the UI + * through dragDividerTo. + * + * @param e The MouseEvent. + */ + protected void continueDrag(MouseEvent e) + { + if (isValid()) + dragDividerTo(positionForMouseEvent(e)); + } + + /** + * This method is called to finish the drag session by calling + * finishDraggingTo. + * + * @param x The x coordinate of the MouseEvent. + * @param y The y coordinate of the MouseEvent. + */ + protected void completeDrag(int x, int y) + { + finishDraggingTo(adjust(x, y)); + } + + /** + * This method is called to finish the drag session by calling + * finishDraggingTo. + * + * @param e The MouseEvent. + */ + protected void completeDrag(MouseEvent e) + { + finishDraggingTo(positionForMouseEvent(e)); + } + + /** + * This is a helper method that includes the offset in the needed + * location. + * + * @param x The x coordinate of the MouseEvent. + * @param y The y coordinate of the MouseEvent. + * + * @return The needed location adjusted by the offsets. + */ + int adjust(int x, int y) + { + return getNeededLocation(x, y) + getX() - offset; + } + } + + /** + * This is a helper class that controls dragging when the orientation is + * VERTICAL_SPLIT. + */ + protected class VerticalDragController extends DragController + { + /** + * Creates a new VerticalDragController object. + * + * @param e The MouseEvent to initialize with. + */ + protected VerticalDragController(MouseEvent e) + { + super(e); + offset = e.getY(); + } + + /** + * This method returns one of the two parameters given the orientation. In + * this case, it returns y. + * + * @param x The x coordinate of the MouseEvent. + * @param y The y coordinate of the MouseEvent. + * + * @return The y coordinate. + */ + protected int getNeededLocation(int x, int y) + { + return y; + } + + /** + * This method returns the new location of the divider given a MouseEvent. + * + * @param e The MouseEvent. + * + * @return The new location of the divider. + */ + protected int positionForMouseEvent(MouseEvent e) + { + return e.getY() + getY() - offset; + } + + /** + * This is a helper method that includes the offset in the needed + * location. + * + * @param x The x coordinate of the MouseEvent. + * @param y The y coordinate of the MouseEvent. + * + * @return The needed location adjusted by the offsets. + */ + int adjust(int x, int y) + { + return getNeededLocation(x, y) + getY() - offset; + } + } + + /** + * This helper class acts as the Layout Manager for the divider. + */ + protected class DividerLayout implements LayoutManager + { + /** + * Creates a new DividerLayout object. + */ + protected DividerLayout() + { + // Nothing to do here. + } + + /** + * This method is called when a Component is added. + * + * @param string The constraints string. + * @param c The Component to add. + */ + public void addLayoutComponent(String string, Component c) + { + // Do nothing. + } + + /** + * This method is called to lay out the container. + * + * @param c The container to lay out. + */ + public void layoutContainer(Container c) + { + if (leftButton != null && rightButton != null + && c == BasicSplitPaneDivider.this) + { + if (splitPane.isOneTouchExpandable()) + { + Insets insets = getInsets(); + if (orientation == JSplitPane.HORIZONTAL_SPLIT) + { + int size = getWidth() - insets.left - insets.right; + size = Math.max(size, 0); + size = Math.min(size, ONE_TOUCH_SIZE); + int x, y; + if (centerOneTouchButtons) + { + y = insets.top; + x = (getWidth() - size) / 2; + } + else + { + x = insets.left; + y = 0; + } + + leftButton.setBounds(x, y + ONE_TOUCH_OFFSET, size, + size * 2); + rightButton.setBounds(x, y + ONE_TOUCH_OFFSET + + ONE_TOUCH_SIZE * 2, size, size * 2); + } + else + { + int size = getHeight() - insets.top - insets.bottom; + size = Math.max(size, 0); + size = Math.min(size, ONE_TOUCH_SIZE); + int x, y; + if (centerOneTouchButtons) + { + x = insets.left; + y = (getHeight() - size) / 2; + } + else + { + x = 0; + y = insets.top; + } + leftButton.setBounds(x + ONE_TOUCH_OFFSET, y, size * 2, + size); + rightButton.setBounds(x + ONE_TOUCH_OFFSET + + ONE_TOUCH_SIZE * 2, y, size * 2, + size); + } + } + else + { + // The JDK sets this bounds for disabled one touch buttons, so + // do we. + leftButton.setBounds(-5, -5, 1, 1); + rightButton.setBounds(-5, -5, 1, 1); + } + } + } + + /** + * This method returns the minimum layout size. + * + * @param c The container to calculate for. + * + * @return The minimum layout size. + */ + public Dimension minimumLayoutSize(Container c) + { + return preferredLayoutSize(c); + } + + /** + * This method returns the preferred layout size. + * + * @param c The container to calculate for. + * + * @return The preferred layout size. + */ + public Dimension preferredLayoutSize(Container c) + { + return new Dimension(dividerSize, dividerSize); + } + + /** + * This method is called when a component is removed. + * + * @param c The component to remove. + */ + public void removeLayoutComponent(Component c) + { + // Do nothing. + } + + } +} diff --git a/libjava/classpath/javax/swing/plaf/basic/BasicSplitPaneUI.java b/libjava/classpath/javax/swing/plaf/basic/BasicSplitPaneUI.java new file mode 100644 index 000000000..ca9607515 --- /dev/null +++ b/libjava/classpath/javax/swing/plaf/basic/BasicSplitPaneUI.java @@ -0,0 +1,1623 @@ +/* BasicSplitPaneUI.java -- + Copyright (C) 2003, 2004, 2005, 2006, 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 java.awt.Canvas; +import java.awt.Color; +import java.awt.Component; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Insets; +import java.awt.LayoutManager2; +import java.awt.Point; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.FocusAdapter; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; + +import javax.swing.AbstractAction; +import javax.swing.ActionMap; +import javax.swing.InputMap; +import javax.swing.JComponent; +import javax.swing.JSlider; +import javax.swing.JSplitPane; +import javax.swing.KeyStroke; +import javax.swing.LookAndFeel; +import javax.swing.SwingConstants; +import javax.swing.SwingUtilities; +import javax.swing.UIManager; +import javax.swing.plaf.ActionMapUIResource; +import javax.swing.plaf.ComponentUI; +import javax.swing.plaf.SplitPaneUI; +import javax.swing.plaf.UIResource; + +/** + * This is the Basic Look and Feel implementation of the SplitPaneUI class. + */ +public class BasicSplitPaneUI extends SplitPaneUI +{ + /** + * This Layout Manager controls the position and size of the components when + * the JSplitPane's orientation is HORIZONTAL_SPLIT. + * + * @specnote Apparently this class was intended to be protected, + * but was made public by a compiler bug and is now + * public for compatibility. + */ + public class BasicHorizontalLayoutManager implements LayoutManager2 + { + // 3 components at a time. + // LEFT/TOP = 0 + // RIGHT/BOTTOM = 1 + // DIVIDER = 2 + + /** + * This array contains the components in the JSplitPane. The left/top + * component is at index 0, the right/bottom is at 1, and the divider is + * at 2. + */ + protected Component[] components = new Component[3]; + + // These are the _current_ widths of the associated component. + + /** + * This array contains the current width (for HORIZONTAL_SPLIT) or height + * (for VERTICAL_SPLIT) of the components. The indices are the same as + * for components. + */ + protected int[] sizes = new int[3]; + + /** + * This is used to determine if we are vertical or horizontal layout. + * In the JDK, the BasicVerticalLayoutManager seems to have no more + * methods implemented (as of JDK5), so we keep this state here. + */ + private int axis; + + /** + * Creates a new instance. This is package private because the reference + * implementation has no public constructor either. Still, we need to + * call it from BasicVerticalLayoutManager. + */ + BasicHorizontalLayoutManager() + { + this(SwingConstants.HORIZONTAL); + } + + /** + * Creates a new instance for a specified axis. This is provided for + * compatibility, since the BasicVerticalLayoutManager seems to have + * no more implementation in the RI, according to the specs. So + * we handle all the axis specific stuff here. + * + * @param a the axis, either SwingConstants#HORIZONTAL, + * or SwingConstants#VERTICAL + */ + BasicHorizontalLayoutManager(int a) + { + axis = a; + } + + /** + * This method adds the component given to the JSplitPane. The position of + * the component is given by the constraints object. + * + * @param comp The Component to add. + * @param constraints The constraints that bind the object. + */ + public void addLayoutComponent(Component comp, Object constraints) + { + addLayoutComponent((String) constraints, comp); + } + + /** + * This method is called to add a Component to the JSplitPane. The + * placement string determines where the Component will be placed. The + * string should be one of LEFT, RIGHT, TOP, BOTTOM or null (signals that + * the component is the divider). + * + * @param place The placement of the Component. + * @param component The Component to add. + * + * @throws IllegalArgumentException DOCUMENT ME! + */ + public void addLayoutComponent(String place, Component component) + { + int i = 0; + if (place == null) + i = 2; + else if (place.equals(JSplitPane.TOP) || place.equals(JSplitPane.LEFT)) + i = 0; + else if (place.equals(JSplitPane.BOTTOM) + || place.equals(JSplitPane.RIGHT)) + i = 1; + else + throw new IllegalArgumentException("Illegal placement in JSplitPane"); + components[i] = component; + resetSizeAt(i); + splitPane.revalidate(); + splitPane.repaint(); + } + + /** + * This method returns the width of the JSplitPane minus the insets. + * + * @param containerSize The Dimensions of the JSplitPane. + * @param insets The Insets of the JSplitPane. + * + * @return The width of the JSplitPane minus the insets. + */ + protected int getAvailableSize(Dimension containerSize, Insets insets) + { + int size; + if (axis == SwingConstants.HORIZONTAL) + size = containerSize.width - insets.left - insets.right; + else + size = containerSize.height - insets.top - insets.bottom; + return size; + } + + /** + * This method returns the given insets left value. If the given inset is + * null, then 0 is returned. + * + * @param insets The Insets to use with the JSplitPane. + * + * @return The inset's left value. + */ + protected int getInitialLocation(Insets insets) + { + int loc = 0; + if (insets != null) + { + if (axis == SwingConstants.HORIZONTAL) + loc = insets.left; + else + loc = insets.top; + } + return loc; + } + + /** + * This specifies how a component is aligned with respect to other + * components in the x fdirection. + * + * @param target The container. + * + * @return The component's alignment. + */ + public float getLayoutAlignmentX(Container target) + { + return 0.0f; + } + + /** + * This specifies how a component is aligned with respect to other + * components in the y direction. + * + * @param target The container. + * + * @return The component's alignment. + */ + public float getLayoutAlignmentY(Container target) + { + return 0.0f; + } + + /** + * This method returns the preferred width of the component. + * + * @param c The component to measure. + * + * @return The preferred width of the component. + */ + protected int getPreferredSizeOfComponent(Component c) + { + int size = 0; + Dimension dims = c.getPreferredSize(); + if (axis == SwingConstants.HORIZONTAL) + { + if (dims != null) + size = dims.width; + } + else + { + if (dims != null) + size = dims.height; + } + return size; + } + + /** + * This method returns the current width of the component. + * + * @param c The component to measure. + * + * @return The width of the component. + */ + protected int getSizeOfComponent(Component c) + { + int size; + if (axis == SwingConstants.HORIZONTAL) + size = c.getHeight(); + else + size = c.getWidth(); + return size; + } + + /** + * This method returns the sizes array. + * + * @return The sizes array. + */ + protected int[] getSizes() + { + return sizes; + } + + /** + * This method invalidates the layout. It does nothing. + * + * @param c The container to invalidate. + */ + public void invalidateLayout(Container c) + { + // DO NOTHING + } + + /** + * This method lays out the components in the container. + * + * @param container The container to lay out. + */ + public void layoutContainer(Container container) + { + if (container instanceof JSplitPane) + { + JSplitPane split = (JSplitPane) container; + distributeExtraSpace(); + Insets insets = split.getInsets(); + Dimension dims = split.getSize(); + int loc = getInitialLocation(insets); + int available = getAvailableSize(dims, insets); + sizes[0] = split.getDividerLocation(); + sizes[1] = available - sizes[0] - sizes[2]; + + // According to a Mauve test we only honour the minimum + // size of the components, when the dividerLocation hasn't + // been excplicitly set. + if (! dividerLocationSet) + { + sizes[0] = Math.max(sizes[0], minimumSizeOfComponent(0)); + sizes[1] = Math.max(sizes[1], minimumSizeOfComponent(1)); + } + // The size of the divider won't change. + + // Layout component#1. + setComponentToSize(components[0], sizes[0], loc, insets, dims); + // Layout divider. + loc += sizes[0]; + setComponentToSize(components[2], sizes[2], loc, insets, dims); + // Layout component#2. + loc += sizes[2]; + setComponentToSize(components[1], sizes[1], loc, insets, dims); + } + } + + /** + * This method returns the maximum size for the container given the + * components. It returns a new Dimension object that has width and + * height equal to Integer.MAX_VALUE. + * + * @param target The container to measure. + * + * @return The maximum size. + */ + public Dimension maximumLayoutSize(Container target) + { + return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE); + } + + /** + * This method returns the container's minimum size. The minimum width is + * the sum of all the component's minimum widths. The minimum height is + * the maximum of all the components' minimum heights. + * + * @param target The container to measure. + * + * @return The minimum size. + */ + public Dimension minimumLayoutSize(Container target) + { + Dimension dim = new Dimension(); + if (target instanceof JSplitPane) + { + int primary = 0; + int secondary = 0; + for (int i = 0; i < components.length; i++) + { + if (components[i] != null) + { + Dimension dims = components[i].getMinimumSize(); + primary += axis == SwingConstants.HORIZONTAL ? dims.width + : dims.height; + int sec = axis == SwingConstants.HORIZONTAL ? dims.height + : dims.width; + secondary = Math.max(sec, secondary); + } + } + int width = axis == SwingConstants.HORIZONTAL ? primary : secondary; + int height = axis == SwingConstants.VERTICAL ? secondary : primary; + + Insets i = splitPane.getInsets(); + dim.setSize(width + i.left + i.right, height + i.top + i.bottom); + } + return dim; + } + + /** + * This method returns the container's preferred size. The preferred width + * is the sum of all the component's preferred widths. The preferred + * height is the maximum of all the components' preferred heights. + * + * @param target The container to measure. + * + * @return The preferred size. + */ + public Dimension preferredLayoutSize(Container target) + { + Dimension dim = new Dimension(); + if (target instanceof JSplitPane) + { + int primary = 0; + int secondary = 0; + for (int i = 0; i < components.length; i++) + { + if (components[i] != null) + { + Dimension dims = components[i].getPreferredSize(); + primary += axis == SwingConstants.HORIZONTAL ? dims.width + : dims.height; + int sec = axis == SwingConstants.HORIZONTAL ? dims.height + : dims.width; + secondary = Math.max(sec, secondary); + } + } + int width = axis == SwingConstants.HORIZONTAL ? primary : secondary; + int height = axis == SwingConstants.VERTICAL ? secondary : primary; + + Insets i = splitPane.getInsets(); + dim.setSize(width + i.left + i.right, height + i.top + i.bottom); + } + return dim; + } + + /** + * This method removes the component from the layout. + * + * @param component The component to remove from the layout. + */ + public void removeLayoutComponent(Component component) + { + for (int i = 0; i < components.length; i++) + { + if (component == components[i]) + { + components[i] = null; + sizes[i] = 0; + } + } + } + + /** + * This method resets the size of Component to the preferred size. + * + * @param index The index of the component to reset. + */ + protected void resetSizeAt(int index) + { + if (components[index] != null) + sizes[index] = getPreferredSizeOfComponent(components[index]); + } + + /** + * This method resets the sizes of all the components. + */ + public void resetToPreferredSizes() + { + for (int i = 0; i < components.length; i++) + resetSizeAt(i); + } + + /** + * This methods sets the bounds of the given component. The width is the + * size. The height is the container size minus the top and bottom + * inset. The x coordinate is the location given. The y coordinate is + * the top inset. + * + * @param c The component to set. + * @param size The width of the component. + * @param location The x coordinate. + * @param insets The insets to use. + * @param containerSize The height of the container. + */ + protected void setComponentToSize(Component c, int size, int location, + Insets insets, Dimension containerSize) + { + if (insets != null) + { + if (axis == SwingConstants.HORIZONTAL) + c.setBounds(location, insets.top, size, + containerSize.height - insets.top - insets.bottom); + else + c.setBounds(insets.left, location, + containerSize.width - insets.left - insets.right, + size); + } + else + { + if (axis == SwingConstants.HORIZONTAL) + c.setBounds(location, 0, size, containerSize.height); + else + c.setBounds(0, location, containerSize.width, size); + } + } + + /** + * This method stores the given int array as the new sizes array. + * + * @param newSizes The array to use as sizes. + */ + protected void setSizes(int[] newSizes) + { + sizes = newSizes; + } + + /** + * This method determines the size of each component. It should be called + * when a new Layout Manager is created for an existing JSplitPane. + */ + protected void updateComponents() + { + Component left = splitPane.getLeftComponent(); + Component right = splitPane.getRightComponent(); + + if (left != null) + { + components[0] = left; + resetSizeAt(0); + } + if (right != null) + { + components[1] = right; + resetSizeAt(1); + } + components[2] = divider; + } + + /** + * This method resizes the left and right components to fit inside the + * JSplitPane when there is extra space. + */ + void distributeExtraSpace() + { + // FIXME: This needs to be reimplemented correctly. + } + + /** + * This method returns the minimum width of the component at the given + * index. + * + * @param index The index to check. + * + * @return The minimum width. + */ + int minimumSizeOfComponent(int index) + { + Dimension dims = components[index].getMinimumSize(); + int size = 0; + if (dims != null) + if (axis == SwingConstants.HORIZONTAL) + size = dims.width; + else + size = dims.height; + return size; + } + } //end BasicHorizontalLayoutManager + + /** + * This class is the Layout Manager for the JSplitPane when the orientation + * is VERTICAL_SPLIT. + * + * @specnote Apparently this class was intended to be protected, + * but was made public by a compiler bug and is now + * public for compatibility. + */ + public class BasicVerticalLayoutManager + extends BasicHorizontalLayoutManager + { + /** + * Creates a new instance. + */ + public BasicVerticalLayoutManager() + { + super(SwingConstants.VERTICAL); + } + } + + /** + * This class handles FocusEvents from the JComponent. + * + * @specnote Apparently this class was intended to be protected, + * but was made public by a compiler bug and is now + * public for compatibility. + */ + public class FocusHandler extends FocusAdapter + { + /** + * This method is called when the JSplitPane gains focus. + * + * @param ev The FocusEvent. + */ + public void focusGained(FocusEvent ev) + { + // repaint the divider because its background color may change due to + // the focus state... + divider.repaint(); + } + + /** + * This method is called when the JSplitPane loses focus. + * + * @param ev The FocusEvent. + */ + public void focusLost(FocusEvent ev) + { + // repaint the divider because its background color may change due to + // the focus state... + divider.repaint(); + } + } + + /** + * This is a deprecated class. It is supposed to be used for handling down + * and right key presses. + * + * @specnote Apparently this class was intended to be protected, + * but was made public by a compiler bug and is now + * public for compatibility. + */ + public class KeyboardDownRightHandler implements ActionListener + { + /** + * This method is called when the down or right keys are pressed. + * + * @param ev The ActionEvent + */ + public void actionPerformed(ActionEvent ev) + { + // FIXME: implement. + } + } + + /** + * This is a deprecated class. It is supposed to be used for handling end + * key presses. + * + * @specnote Apparently this class was intended to be protected, + * but was made public by a compiler bug and is now + * public for compatibility. + */ + public class KeyboardEndHandler implements ActionListener + { + /** + * This method is called when the end key is pressed. + * + * @param ev The ActionEvent. + */ + public void actionPerformed(ActionEvent ev) + { + // FIXME: implement. + } + } + + /** + * This is a deprecated class. It is supposed to be used for handling home + * key presses. + * + * @specnote Apparently this class was intended to be protected, + * but was made public by a compiler bug and is now + * public for compatibility. + */ + public class KeyboardHomeHandler implements ActionListener + { + /** + * This method is called when the home key is pressed. + * + * @param ev The ActionEvent. + */ + public void actionPerformed(ActionEvent ev) + { + // FIXME: implement. + } + } + + /** + * This is a deprecated class. It is supposed to be used for handling resize + * toggles. + * + * @specnote Apparently this class was intended to be protected, + * but was made public by a compiler bug and is now + * public for compatibility. + */ + public class KeyboardResizeToggleHandler implements ActionListener + { + /** + * This method is called when a resize is toggled. + * + * @param ev The ActionEvent. + */ + public void actionPerformed(ActionEvent ev) + { + // FIXME: implement. + } + } + + /** + * This is a deprecated class. It is supposed to be used for handler up and + * left key presses. + * + * @specnote Apparently this class was intended to be protected, + * but was made public by a compiler bug and is now + * public for compatibility. + */ + public class KeyboardUpLeftHandler implements ActionListener + { + /** + * This method is called when the left or up keys are pressed. + * + * @param ev The ActionEvent. + */ + public void actionPerformed(ActionEvent ev) + { + // FIXME: implement. + } + } + + /** + * This helper class handles PropertyChangeEvents from the JSplitPane. When + * a property changes, this will update the UI accordingly. + * + * @specnote Apparently this class was intended to be protected, + * but was made public by a compiler bug and is now + * public for compatibility. + */ + public class PropertyHandler implements PropertyChangeListener + { + /** + * This method is called whenever one of the JSplitPane's properties + * change. + * + * @param e DOCUMENT ME! + */ + public void propertyChange(PropertyChangeEvent e) + { + if (e.getPropertyName().equals(JSplitPane.DIVIDER_SIZE_PROPERTY)) + { + int newSize = splitPane.getDividerSize(); + int[] tmpSizes = layoutManager.getSizes(); + dividerSize = tmpSizes[2]; + int newSpace = newSize - tmpSizes[2]; + tmpSizes[2] = newSize; + + tmpSizes[0] += newSpace / 2; + tmpSizes[1] += newSpace / 2; + + layoutManager.setSizes(tmpSizes); + } + else if (e.getPropertyName().equals(JSplitPane.ORIENTATION_PROPERTY)) + { + int max = layoutManager.getAvailableSize(splitPane.getSize(), + splitPane.getInsets()); + int dividerLoc = getDividerLocation(splitPane); + double prop = ((double) dividerLoc) / max; + + resetLayoutManager(); + if (prop <= 1 && prop >= 0) + splitPane.setDividerLocation(prop); + } + // Don't have to deal with continuous_layout - only + // necessary in dragging modes (and it's checked + // every time you drag there) + // Don't have to deal with resize_weight (as there + // will be no extra space associated with this + // event - the changes to the weighting will + // be taken into account the next time the + // sizes change.) + // Don't have to deal with divider_location + // The method in JSplitPane calls our setDividerLocation + // so we'll know about those anyway. + // Don't have to deal with last_divider_location + // Although I'm not sure why, it doesn't seem to + // have any effect on Sun's JSplitPane. + // one_touch_expandable changes are dealt with + // by our divider. + } + } + + /** The location of the divider when dragging began. */ + protected int beginDragDividerLocation; + + /** The size of the divider while dragging. */ + protected int dividerSize; + + /** The location where the last drag location ended. */ + transient int lastDragLocation = -1; + + /** The distance the divider is moved when moved by keyboard actions. */ + // Sun defines this as 3 + protected static int KEYBOARD_DIVIDER_MOVE_OFFSET = 3; + + /** The divider that divides this JSplitPane. */ + protected BasicSplitPaneDivider divider; + + /** The listener that listens for PropertyChangeEvents from the JSplitPane. */ + protected PropertyChangeListener propertyChangeListener; + + /** The JSplitPane's focus handler. */ + protected FocusListener focusListener; + + /** @deprecated The handler for down and right key presses. */ + protected ActionListener keyboardDownRightListener; + + /** @deprecated The handler for end key presses. */ + protected ActionListener keyboardEndListener; + + /** @deprecated The handler for home key presses. */ + protected ActionListener keyboardHomeListener; + + /** @deprecated The handler for toggling resizes. */ + protected ActionListener keyboardResizeToggleListener; + + /** @deprecated The handler for up and left key presses. */ + protected ActionListener keyboardUpLeftListener; + + /** The JSplitPane's current layout manager. */ + protected BasicHorizontalLayoutManager layoutManager; + + /** @deprecated The divider resize toggle key. */ + protected KeyStroke dividerResizeToggleKey; + + /** @deprecated The down key. */ + protected KeyStroke downKey; + + /** @deprecated The end key. */ + protected KeyStroke endKey; + + /** @deprecated The home key. */ + protected KeyStroke homeKey; + + /** @deprecated The left key. */ + protected KeyStroke leftKey; + + /** @deprecated The right key. */ + protected KeyStroke rightKey; + + /** @deprecated The up key. */ + protected KeyStroke upKey; + + /** Set to true when dragging heavy weight components. */ + protected boolean draggingHW; + + /** + * The constraints object used when adding the non-continuous divider to the + * JSplitPane. + */ + protected static final String NON_CONTINUOUS_DIVIDER + = "nonContinuousDivider"; + + /** The dark divider used when dragging in non-continuous layout mode. */ + protected Component nonContinuousLayoutDivider; + + /** The JSplitPane that this UI draws. */ + protected JSplitPane splitPane; + + /** + * True, when setDividerLocation() has been called at least + * once on the JSplitPane, false otherwise. + * + * This is package private to avoid a synthetic accessor method. + */ + boolean dividerLocationSet; + + /** + * Creates a new BasicSplitPaneUI object. + */ + public BasicSplitPaneUI() + { + // Nothing to do here. + } + + /** + * This method creates a new BasicSplitPaneUI for the given JComponent. + * + * @param x The JComponent to create a UI for. + * + * @return A new BasicSplitPaneUI. + */ + public static ComponentUI createUI(JComponent x) + { + return new BasicSplitPaneUI(); + } + + /** + * This method installs the BasicSplitPaneUI for the given JComponent. + * + * @param c The JComponent to install the UI for. + */ + public void installUI(JComponent c) + { + if (c instanceof JSplitPane) + { + splitPane = (JSplitPane) c; + dividerLocationSet = false; + installDefaults(); + installListeners(); + installKeyboardActions(); + } + } + + /** + * This method uninstalls the BasicSplitPaneUI for the given JComponent. + * + * @param c The JComponent to uninstall the UI for. + */ + public void uninstallUI(JComponent c) + { + uninstallKeyboardActions(); + uninstallListeners(); + uninstallDefaults(); + + dividerLocationSet = false; + splitPane = null; + } + + /** + * This method installs the defaults given by the Look and Feel. + */ + protected void installDefaults() + { + LookAndFeel.installColors(splitPane, "SplitPane.background", + "SplitPane.foreground"); + LookAndFeel.installBorder(splitPane, "SplitPane.border"); + divider = createDefaultDivider(); + divider.setBorder(UIManager.getBorder("SplitPaneDivider.border")); + resetLayoutManager(); + nonContinuousLayoutDivider = createDefaultNonContinuousLayoutDivider(); + splitPane.add(divider, JSplitPane.DIVIDER); + + // There is no need to add the nonContinuousLayoutDivider. + dividerSize = UIManager.getInt("SplitPane.dividerSize"); + splitPane.setDividerSize(dividerSize); + divider.setDividerSize(dividerSize); + splitPane.setOpaque(true); + } + + /** + * This method uninstalls the defaults and nulls any objects created during + * install. + */ + protected void uninstallDefaults() + { + layoutManager = null; + splitPane.remove(divider); + divider = null; + nonContinuousLayoutDivider = null; + + if (splitPane.getBackground() instanceof UIResource) + splitPane.setBackground(null); + if (splitPane.getBorder() instanceof UIResource) + splitPane.setBorder(null); + } + + /** + * This method installs the listeners needed for this UI to function. + */ + protected void installListeners() + { + propertyChangeListener = createPropertyChangeListener(); + focusListener = createFocusListener(); + + splitPane.addPropertyChangeListener(propertyChangeListener); + splitPane.addFocusListener(focusListener); + } + + /** + * This method uninstalls all listeners registered for the UI. + */ + protected void uninstallListeners() + { + splitPane.removePropertyChangeListener(propertyChangeListener); + splitPane.removeFocusListener(focusListener); + + focusListener = null; + propertyChangeListener = null; + } + + /** + * Returns the input map for the specified condition. + * + * @param condition the condition. + * + * @return The input map. + */ + InputMap getInputMap(int condition) + { + if (condition == JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT) + return (InputMap) UIManager.get("SplitPane.ancestorInputMap"); + return null; + } + + /** + * Returns the action map for the {@link JSplitPane}. All sliders share + * a single action map which is created the first time this method is + * called, then stored in the UIDefaults table for subsequent access. + * + * @return The shared action map. + */ + ActionMap getActionMap() + { + ActionMap map = (ActionMap) UIManager.get("SplitPane.actionMap"); + + if (map == null) // first time here + { + map = createActionMap(); + if (map != null) + UIManager.put("SplitPane.actionMap", map); + } + return map; + } + + /** + * Creates the action map shared by all {@link JSlider} instances. + * This method is called once by {@link #getActionMap()} when it + * finds no action map in the UIDefaults table...after the map is + * created, it gets added to the defaults table so that subsequent + * calls to {@link #getActionMap()} will return the same shared + * instance. + * + * @return The action map. + */ + ActionMap createActionMap() + { + ActionMap map = new ActionMapUIResource(); + map.put("toggleFocus", + new AbstractAction("toggleFocus") { + public void actionPerformed(ActionEvent event) + { + // FIXME: What to do here? + } + } + ); + map.put("startResize", + new AbstractAction("startResize") { + public void actionPerformed(ActionEvent event) + { + splitPane.requestFocus(); + } + } + ); + map.put("selectMax", + new AbstractAction("selectMax") { + public void actionPerformed(ActionEvent event) + { + splitPane.setDividerLocation(1.0); + } + } + ); + map.put("selectMin", + new AbstractAction("selectMin") { + public void actionPerformed(ActionEvent event) + { + splitPane.setDividerLocation(0.0); + } + } + ); + map.put("negativeIncrement", + new AbstractAction("negativeIncrement") { + public void actionPerformed(ActionEvent event) + { + int oldLoc = splitPane.getDividerLocation(); + int newLoc = + Math.max(oldLoc - KEYBOARD_DIVIDER_MOVE_OFFSET, 0); + splitPane.setDividerLocation(newLoc); + } + } + ); + map.put("positiveIncrement", + new AbstractAction("positiveIncrement") { + public void actionPerformed(ActionEvent event) + { + int oldLoc = splitPane.getDividerLocation(); + int newLoc = + Math.max(oldLoc + KEYBOARD_DIVIDER_MOVE_OFFSET, 0); + splitPane.setDividerLocation(newLoc); + } + } + ); + map.put("focusOutBackward", + new AbstractAction("focusOutBackward") { + public void actionPerformed(ActionEvent event) + { + // FIXME: implement this + } + } + ); + map.put("focusOutForward", + new AbstractAction("focusOutForward") { + public void actionPerformed(ActionEvent event) + { + // FIXME: implement this + } + } + ); + return map; + } + + /** + * Installs any keyboard actions. The list of keys that need to be bound are + * listed in Basic look and feel's defaults. + */ + protected void installKeyboardActions() + { + InputMap keyMap = getInputMap( + JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); + SwingUtilities.replaceUIInputMap(splitPane, + JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, keyMap); + ActionMap map = getActionMap(); + SwingUtilities.replaceUIActionMap(splitPane, map); + } + + /** + * This method reverses the work done in installKeyboardActions. + */ + protected void uninstallKeyboardActions() + { + SwingUtilities.replaceUIActionMap(splitPane, null); + SwingUtilities.replaceUIInputMap(splitPane, + JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, null); + } + + /** + * This method creates a new PropertyChangeListener. + * + * @return A new PropertyChangeListener. + */ + protected PropertyChangeListener createPropertyChangeListener() + { + return new PropertyHandler(); + } + + /** + * This method creates a new FocusListener. + * + * @return A new FocusListener. + */ + protected FocusListener createFocusListener() + { + return new FocusHandler(); + } + + /** + * This method creates a new ActionListener for up and left key presses. + * + * @return A new ActionListener for up and left keys. + * + * @deprecated 1.3 + */ + protected ActionListener createKeyboardUpLeftListener() + { + return new KeyboardUpLeftHandler(); + } + + /** + * This method creates a new ActionListener for down and right key presses. + * + * @return A new ActionListener for down and right keys. + * + * @deprecated 1.3 + */ + protected ActionListener createKeyboardDownRightListener() + { + return new KeyboardDownRightHandler(); + } + + /** + * This method creates a new ActionListener for home key presses. + * + * @return A new ActionListener for home keys. + * + * @deprecated + */ + protected ActionListener createKeyboardHomeListener() + { + return new KeyboardHomeHandler(); + } + + /** + * This method creates a new ActionListener for end key presses.i + * + * @return A new ActionListener for end keys. + * + * @deprecated 1.3 + */ + protected ActionListener createKeyboardEndListener() + { + return new KeyboardEndHandler(); + } + + /** + * This method creates a new ActionListener for resize toggle key events. + * + * @return A new ActionListener for resize toggle keys. + * + * @deprecated 1.3 + */ + protected ActionListener createKeyboardResizeToggleListener() + { + return new KeyboardResizeToggleHandler(); + } + + /** + * This method returns the orientation of the JSplitPane. + * + * @return The orientation of the JSplitPane. + */ + public int getOrientation() + { + return splitPane.getOrientation(); + } + + /** + * This method sets the orientation of the JSplitPane. + * + * @param orientation The new orientation of the JSplitPane. + */ + public void setOrientation(int orientation) + { + splitPane.setOrientation(orientation); + } + + /** + * This method returns true if the JSplitPane is using continuous layout. + * + * @return True if the JSplitPane is using continuous layout. + */ + public boolean isContinuousLayout() + { + return splitPane.isContinuousLayout(); + } + + /** + * This method sets the continuous layout property of the JSplitPane. + * + * @param b True if the JsplitPane is to use continuous layout. + */ + public void setContinuousLayout(boolean b) + { + splitPane.setContinuousLayout(b); + } + + /** + * This method returns the last location the divider was dragged to. + * + * @return The last location the divider was dragged to. + */ + public int getLastDragLocation() + { + return lastDragLocation; + } + + /** + * This method sets the last location the divider was dragged to. + * + * @param l The last location the divider was dragged to. + */ + public void setLastDragLocation(int l) + { + lastDragLocation = l; + } + + /** + * This method returns the BasicSplitPaneDivider that divides this + * JSplitPane. + * + * @return The divider for the JSplitPane. + */ + public BasicSplitPaneDivider getDivider() + { + return divider; + } + + /** + * This method creates a nonContinuousLayoutDivider for use with the + * JSplitPane in nonContinousLayout mode. The default divider is a gray + * Canvas. + * + * @return The default nonContinousLayoutDivider. + */ + protected Component createDefaultNonContinuousLayoutDivider() + { + if (nonContinuousLayoutDivider == null) + { + nonContinuousLayoutDivider = new Canvas(); + Color c = UIManager.getColor("SplitPaneDivider.draggingColor"); + nonContinuousLayoutDivider.setBackground(c); + } + return nonContinuousLayoutDivider; + } + + /** + * This method sets the component to use as the nonContinuousLayoutDivider. + * + * @param newDivider The component to use as the nonContinuousLayoutDivider. + */ + protected void setNonContinuousLayoutDivider(Component newDivider) + { + setNonContinuousLayoutDivider(newDivider, true); + } + + /** + * This method sets the component to use as the nonContinuousLayoutDivider. + * + * @param newDivider The component to use as the nonContinuousLayoutDivider. + * @param rememberSizes FIXME: document. + */ + protected void setNonContinuousLayoutDivider(Component newDivider, + boolean rememberSizes) + { + // FIXME: use rememberSizes for something + nonContinuousLayoutDivider = newDivider; + } + + /** + * This method returns the nonContinuousLayoutDivider. + * + * @return The nonContinuousLayoutDivider. + */ + public Component getNonContinuousLayoutDivider() + { + return nonContinuousLayoutDivider; + } + + /** + * This method returns the JSplitPane that this BasicSplitPaneUI draws. + * + * @return The JSplitPane. + */ + public JSplitPane getSplitPane() + { + return splitPane; + } + + /** + * This method creates the divider used normally with the JSplitPane. + * + * @return The default divider. + */ + public BasicSplitPaneDivider createDefaultDivider() + { + if (divider == null) + divider = new BasicSplitPaneDivider(this); + return divider; + } + + /** + * This method is called when JSplitPane's resetToPreferredSizes is called. + * It resets the sizes of all components in the JSplitPane. + * + * @param jc The JSplitPane to reset. + */ + public void resetToPreferredSizes(JSplitPane jc) + { + layoutManager.resetToPreferredSizes(); + } + + /** + * This method sets the location of the divider. + * + * @param jc The JSplitPane to set the divider location in. + * @param location The new location of the divider. + */ + public void setDividerLocation(JSplitPane jc, int location) + { + dividerLocationSet = true; + splitPane.revalidate(); + splitPane.repaint(); + } + + /** + * This method returns the location of the divider. + * + * @param jc The JSplitPane to retrieve the location for. + * + * @return The location of the divider. + */ + public int getDividerLocation(JSplitPane jc) + { + int loc; + if (jc.getOrientation() == JSplitPane.HORIZONTAL_SPLIT) + loc = divider.getX(); + else + loc = divider.getY(); + return loc; + } + + /** + * This method returns the smallest value possible for the location of the + * divider. + * + * @param jc The JSplitPane. + * + * @return The minimum divider location. + */ + public int getMinimumDividerLocation(JSplitPane jc) + { + int value = layoutManager.getInitialLocation(jc.getInsets()); + if (layoutManager.components[0] != null) + value += layoutManager.minimumSizeOfComponent(0); + return value; + } + + /** + * This method returns the largest value possible for the location of the + * divider. + * + * @param jc The JSplitPane. + * + * @return The maximum divider location. + */ + public int getMaximumDividerLocation(JSplitPane jc) + { + int value = layoutManager.getInitialLocation(jc.getInsets()) + + layoutManager.getAvailableSize(jc.getSize(), jc.getInsets()) + - splitPane.getDividerSize(); + if (layoutManager.components[1] != null) + value -= layoutManager.minimumSizeOfComponent(1); + return value; + } + + /** + * This method is called after the children of the JSplitPane are painted. + * + * @param jc The JSplitPane. + * @param g The Graphics object to paint with. + */ + public void finishedPaintingChildren(JSplitPane jc, Graphics g) + { + if (! splitPane.isContinuousLayout() && nonContinuousLayoutDivider != null + && nonContinuousLayoutDivider.isVisible()) + javax.swing.SwingUtilities.paintComponent(g, nonContinuousLayoutDivider, + null, + nonContinuousLayoutDivider + .getBounds()); + } + + /** + * This method is called to paint the JSplitPane. + * + * @param g The Graphics object to paint with. + * @param jc The JSplitPane to paint. + */ + public void paint(Graphics g, JComponent jc) + { + // TODO: What should be done here? + } + + /** + * This method returns the preferred size of the JSplitPane. + * + * @param jc The JSplitPane. + * + * @return The preferred size of the JSplitPane. + */ + public Dimension getPreferredSize(JComponent jc) + { + return layoutManager.preferredLayoutSize(jc); + } + + /** + * This method returns the minimum size of the JSplitPane. + * + * @param jc The JSplitPane. + * + * @return The minimum size of the JSplitPane. + */ + public Dimension getMinimumSize(JComponent jc) + { + return layoutManager.minimumLayoutSize(jc); + } + + /** + * This method returns the maximum size of the JSplitPane. + * + * @param jc The JSplitPane. + * + * @return The maximum size of the JSplitPane. + */ + public Dimension getMaximumSize(JComponent jc) + { + return layoutManager.maximumLayoutSize(jc); + } + + /** + * This method returns the border insets of the current border. + * + * @param jc The JSplitPane. + * + * @return The current border insets. + */ + public Insets getInsets(JComponent jc) + { + return splitPane.getBorder().getBorderInsets(splitPane); + } + + /** + * This method resets the current layout manager. The type of layout manager + * is dependent on the current orientation. + */ + protected void resetLayoutManager() + { + if (getOrientation() == JSplitPane.HORIZONTAL_SPLIT) + layoutManager = new BasicHorizontalLayoutManager(); + else + layoutManager = new BasicVerticalLayoutManager(); + getSplitPane().setLayout(layoutManager); + layoutManager.updateComponents(); + + // invalidating by itself does not invalidate the layout. + getSplitPane().revalidate(); + } + + /** + * This method is called when dragging starts. It resets lastDragLocation + * and dividerSize. + */ + protected void startDragging() + { + Component left = splitPane.getLeftComponent(); + Component right = splitPane.getRightComponent(); + dividerSize = divider.getDividerSize(); + setLastDragLocation(-1); + + if ((left != null && !left.isLightweight()) + || (right != null && !right.isLightweight())) + draggingHW = true; + + if (splitPane.isContinuousLayout()) + nonContinuousLayoutDivider.setVisible(false); + else + { + nonContinuousLayoutDivider.setVisible(true); + nonContinuousLayoutDivider.setBounds(divider.getBounds()); + } + } + + /** + * This method is called whenever the divider is dragged. If the JSplitPane + * is in continuousLayout mode, the divider needs to be moved and the + * JSplitPane needs to be laid out. + * + * @param location The new location of the divider. + */ + protected void dragDividerTo(int location) + { + location = validLocation(location); + if (beginDragDividerLocation == -1) + beginDragDividerLocation = location; + + if (splitPane.isContinuousLayout()) + splitPane.setDividerLocation(location); + else + { + Point p = nonContinuousLayoutDivider.getLocation(); + if (getOrientation() == JSplitPane.HORIZONTAL_SPLIT) + p.x = location; + else + p.y = location; + nonContinuousLayoutDivider.setLocation(p); + } + setLastDragLocation(location); + splitPane.repaint(); + } + + /** + * This method is called when the dragging is finished. + * + * @param location The location where the drag finished. + */ + protected void finishDraggingTo(int location) + { + if (nonContinuousLayoutDivider != null) + nonContinuousLayoutDivider.setVisible(false); + draggingHW = false; + location = validLocation(location); + splitPane.setDividerLocation(location); + splitPane.setLastDividerLocation(beginDragDividerLocation); + beginDragDividerLocation = -1; + } + + /** + * This method returns the width of one of the sides of the divider's border. + * + * @return The width of one side of the divider's border. + * + * @deprecated 1.3 + */ + protected int getDividerBorderSize() + { + if (getOrientation() == JSplitPane.HORIZONTAL_SPLIT) + return divider.getBorder().getBorderInsets(divider).left; + else + return divider.getBorder().getBorderInsets(divider).top; + } + + /** + * This is a helper method that returns a valid location for the divider + * when dragging. + * + * @param location The location to check. + * + * @return A valid location. + */ + private int validLocation(int location) + { + int min = getMinimumDividerLocation(splitPane); + int max = getMaximumDividerLocation(splitPane); + if (min > 0 && location < min) + return min; + if (max > 0 && location > max) + return max; + return location; + } +} diff --git a/libjava/classpath/javax/swing/plaf/basic/BasicTabbedPaneUI.java b/libjava/classpath/javax/swing/plaf/basic/BasicTabbedPaneUI.java new file mode 100644 index 000000000..c42f9caed --- /dev/null +++ b/libjava/classpath/javax/swing/plaf/basic/BasicTabbedPaneUI.java @@ -0,0 +1,4003 @@ +/* BasicTabbedPaneUI.java -- + Copyright (C) 2002, 2004, 2005, 2006 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 java.awt.Color; +import java.awt.Component; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.FontMetrics; +import java.awt.Graphics; +import java.awt.Insets; +import java.awt.LayoutManager; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.event.ActionEvent; +import java.awt.event.FocusAdapter; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; + +import javax.swing.AbstractAction; +import javax.swing.ActionMap; +import javax.swing.Icon; +import javax.swing.InputMap; +import javax.swing.JComponent; +import javax.swing.JPanel; +import javax.swing.JTabbedPane; +import javax.swing.JViewport; +import javax.swing.KeyStroke; +import javax.swing.LookAndFeel; +import javax.swing.SwingConstants; +import javax.swing.SwingUtilities; +import javax.swing.UIManager; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import javax.swing.plaf.ActionMapUIResource; +import javax.swing.plaf.ComponentUI; +import javax.swing.plaf.TabbedPaneUI; +import javax.swing.plaf.UIResource; +import javax.swing.text.View; + +/** + * This is the Basic Look and Feel's UI delegate for JTabbedPane. + * + * @author Lillian Angel (langel@redhat.com) + * @author Kim Ho (kho@redhat.com) + * @author Roman Kennke (kennke@aicas.com) + * @author Robert Schuster (robertschuster@fsfe.org) + */ +public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants +{ + + static class NavigateAction extends AbstractAction + { + int direction; + + NavigateAction(String name, int dir) + { + super(name); + direction = dir; + } + + public void actionPerformed(ActionEvent event) + { + JTabbedPane tp = (JTabbedPane) event.getSource(); + BasicTabbedPaneUI ui = (BasicTabbedPaneUI) tp.getUI(); + + ui.navigateSelectedTab(direction); + } + + } + + static class NavigatePageDownAction extends AbstractAction + { + + public NavigatePageDownAction() + { + super("navigatePageDown"); + } + + public void actionPerformed(ActionEvent event) + { + JTabbedPane tp = (JTabbedPane) event.getSource(); + BasicTabbedPaneUI ui = (BasicTabbedPaneUI) tp.getUI(); + + int i = tp.getSelectedIndex(); + + if (i < 0) + i = 0; + + ui.selectNextTabInRun(i); + } + + } + + static class NavigatePageUpAction extends AbstractAction + { + + public NavigatePageUpAction() + { + super("navigatePageUp"); + } + + public void actionPerformed(ActionEvent event) + { + JTabbedPane tp = (JTabbedPane) event.getSource(); + BasicTabbedPaneUI ui = (BasicTabbedPaneUI) tp.getUI(); + + int i = tp.getSelectedIndex(); + + if (i < 0) + i = 0; + + ui.selectPreviousTabInRun(i); + + } + } + + static class RequestFocusAction extends AbstractAction + { + + public RequestFocusAction() + { + super("requestFocus"); + } + + public void actionPerformed(ActionEvent event) + { + ((JTabbedPane) event.getSource()).requestFocus(); + } + + } + + static class RequestFocusForVisibleComponentAction extends AbstractAction + { + + public RequestFocusForVisibleComponentAction() + { + super("requestFocusForVisibleComponent"); + } + + public void actionPerformed(ActionEvent event) + { + JTabbedPane tp = (JTabbedPane) event.getSource(); + + // FIXME: This should select a suitable component within + // the tab content. However I dont know whether we have + // to search for this component or wether the called is + // supposed to do that. + tp.getSelectedComponent().requestFocus(); + } + + } + + /** + * A helper class that handles focus. + * <p>The purpose of this class is to implement a more flexible focus + * handling for the tabbed pane, which is used to determine whether the + * focus indicator should be painted or not. When in scrolling layout + * mode the area containing the tabs is a scrollpane, so simply testing + * whether the tabbed pane has the focus does not work.</p> + * <p>The <code>FocusHandler</code> is installed on the scrollpane and + * the tabbed pane and sets the variable <code>hasFocus</code> to + * <code>false</code> only when both components do not hold the focus.</p> + * + * @specnote Apparently this class was intended to be protected, + * but was made public by a compiler bug and is now + * public for compatibility. + */ + public class FocusHandler extends FocusAdapter + { + /** + * This method is called when the component gains focus. + * + * @param e The FocusEvent. + */ + public void focusGained(FocusEvent e) + { + Object source = e.getSource(); + if (source == panel ) + tabPane.requestFocus(); + else if (source == tabPane) + tabPane.repaint(); + } + + /** + * This method is called when the component loses focus. + * + * @param e The FocusEvent. + */ + public void focusLost(FocusEvent e) + { + if (e.getOppositeComponent() == tabPane.getSelectedComponent()) + tabPane.requestFocus(); + else if (e.getSource() == tabPane) + tabPane.repaint(); + } + } + + /** + * A helper class for determining if mouse presses occur inside tabs and + * sets the index appropriately. In SCROLL_TAB_MODE, this class also + * handles the mouse clicks on the scrolling buttons. + * + * @specnote Apparently this class was intended to be protected, + * but was made public by a compiler bug and is now + * public for compatibility. + */ + public class MouseHandler extends MouseAdapter + { + public void mouseReleased(MouseEvent e) + { + Object s = e.getSource(); + + // Event may originate from the viewport in + // SCROLL_TAB_LAYOUT mode. It is redisptached + // through the tabbed pane then. + if (tabPane != e.getSource()) + { + redispatchEvent(e); + e.setSource(s); + } + } + + /** + * This method is called when the mouse is pressed. The index cannot + * change to a tab that is not enabled. + * + * @param e The MouseEvent. + */ + public void mousePressed(MouseEvent e) + { + Object s = e.getSource(); + + // Event may originate from the viewport in + // SCROLL_TAB_LAYOUT mode. It is redisptached + // through the tabbed pane then. + if (tabPane != e.getSource()) + { + redispatchEvent(e); + e.setSource(s); + } + + int placement = tabPane.getTabPlacement(); + + if (s == incrButton) + { + if(!incrButton.isEnabled()) + return; + + currentScrollLocation++; + + switch (placement) + { + case JTabbedPane.TOP: + case JTabbedPane.BOTTOM: + currentScrollOffset = getTabAreaInsets(placement).left; + for (int i = 0; i < currentScrollLocation; i++) + currentScrollOffset += rects[i].width; + break; + default: + currentScrollOffset = getTabAreaInsets(placement).top; + for (int i = 0; i < currentScrollLocation; i++) + currentScrollOffset += rects[i].height; + break; + } + + updateViewPosition(); + updateButtons(); + + tabPane.repaint(); + } + else if (s == decrButton) + { + if(!decrButton.isEnabled()) + return; + + // The scroll location may be zero but the offset + // greater than zero because of an adjustement to + // make a partially visible tab completely visible. + if (currentScrollLocation > 0) + currentScrollLocation--; + + // Set the offset back to 0 and recompute it. + currentScrollOffset = 0; + + switch (placement) + { + case JTabbedPane.TOP: + case JTabbedPane.BOTTOM: + // Take the tab area inset into account. + if (currentScrollLocation > 0) + currentScrollOffset = getTabAreaInsets(placement).left; + // Recompute scroll offset. + for (int i = 0; i < currentScrollLocation; i++) + currentScrollOffset += rects[i].width; + break; + default: + // Take the tab area inset into account. + if (currentScrollLocation > 0) + currentScrollOffset = getTabAreaInsets(placement).top; + + for (int i = 0; i < currentScrollLocation; i++) + currentScrollOffset += rects[i].height; + } + + updateViewPosition(); + updateButtons(); + + tabPane.repaint(); + } + else if (tabPane.isEnabled()) + { + int index = tabForCoordinate(tabPane, e.getX(), e.getY()); + if (!tabPane.isEnabledAt(index)) + return; + + if (tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT + && s == panel) + { + scrollTab(index, placement); + + tabPane.setSelectedIndex(index); + tabPane.repaint(); + } + else + { + tabPane.setSelectedIndex(index); + tabPane.revalidate(); + tabPane.repaint(); + } + + } + + } + + /** + * Receives notification when the mouse pointer has entered the tabbed + * pane. + * + * @param e the mouse event + */ + public void mouseEntered(MouseEvent e) + { + Object s = e.getSource(); + + // Event may originate from the viewport in + // SCROLL_TAB_LAYOUT mode. It is redisptached + // through the tabbed pane then. + if (tabPane != e.getSource()) + { + redispatchEvent(e); + e.setSource(s); + } + + int tabIndex = tabForCoordinate(tabPane, e.getX(), e.getY()); + setRolloverTab(tabIndex); + } + + /** + * Receives notification when the mouse pointer has exited the tabbed + * pane. + * + * @param e the mouse event + */ + public void mouseExited(MouseEvent e) + { + Object s = e.getSource(); + + // Event may originate from the viewport in + // SCROLL_TAB_LAYOUT mode. It is redisptached + // through the tabbed pane then. + if (tabPane != e.getSource()) + { + redispatchEvent(e); + e.setSource(s); + } + + setRolloverTab(-1); + } + + /** + * Receives notification when the mouse pointer has moved over the tabbed + * pane. + * + * @param ev the mouse event + */ + public void mouseMoved(MouseEvent ev) + { + Object s = ev.getSource(); + + if (tabPane != ev.getSource()) + { + ev.setSource(tabPane); + tabPane.dispatchEvent(ev); + + ev.setSource(s); + } + + int tabIndex = tabForCoordinate(tabPane, ev.getX(), ev.getY()); + setRolloverTab(tabIndex); + } + + /** Modifies the mouse event to originate from + * the tabbed pane and redispatches it. + * + * @param me + */ + void redispatchEvent(MouseEvent me) + { + me.setSource(tabPane); + Point viewPos = viewport.getViewPosition(); + viewPos.x -= viewport.getX(); + viewPos.y -= viewport.getY(); + me.translatePoint(-viewPos.x, -viewPos.y); + tabPane.dispatchEvent(me); + + me.translatePoint(viewPos.x, viewPos.y); + } + + } + + /** + * This class handles PropertyChangeEvents fired from the JTabbedPane. + * + * @specnote Apparently this class was intended to be protected, + * but was made public by a compiler bug and is now + * public for compatibility. + */ + public class PropertyChangeHandler implements PropertyChangeListener + { + /** + * This method is called whenever one of the properties of the JTabbedPane + * changes. + * + * @param e The PropertyChangeEvent. + */ + public void propertyChange(PropertyChangeEvent e) + { + out: + { + if (e.getPropertyName().equals("tabLayoutPolicy")) + { + currentScrollLocation = currentScrollOffset = 0; + + layoutManager = createLayoutManager(); + + tabPane.setLayout(layoutManager); + } + else if (e.getPropertyName().equals("tabPlacement") + && tabPane.getTabLayoutPolicy() + == JTabbedPane.SCROLL_TAB_LAYOUT) + { + incrButton = createIncreaseButton(); + decrButton = createDecreaseButton(); + + // If the tab placement value was changed of a tabbed pane + // in SCROLL_TAB_LAYOUT mode we investigate the change to + // implement the following behavior which was observed in + // the RI: + // The scrolling offset will be reset if we change to + // a direction which is orthogonal to the current + // direction and stays the same if it is parallel. + + int oldPlacement = ((Integer) e.getOldValue()).intValue(); + int newPlacement = ((Integer) e.getNewValue()).intValue(); + switch (newPlacement) + { + case JTabbedPane.TOP: + case JTabbedPane.BOTTOM: + if (oldPlacement == JTabbedPane.TOP + || oldPlacement == JTabbedPane.BOTTOM) + break out; + + currentScrollOffset = getTabAreaInsets(newPlacement).left; + break; + default: + if (oldPlacement == JTabbedPane.LEFT + || oldPlacement == JTabbedPane.RIGHT) + break out; + + currentScrollOffset = getTabAreaInsets(newPlacement).top; + } + + updateViewPosition(); + updateButtons(); + } + } + + tabPane.revalidate(); + tabPane.repaint(); + } + } + + /** + * A LayoutManager responsible for placing all the tabs and the visible + * component inside the JTabbedPane. This class is only used for + * WRAP_TAB_LAYOUT. + * + * @specnote Apparently this class was intended to be protected, + * but was made public by a compiler bug and is now + * public for compatibility. + */ + public class TabbedPaneLayout implements LayoutManager + { + /** + * This method is called when a component is added to the JTabbedPane. + * + * @param name The name of the component. + * @param comp The component being added. + */ + public void addLayoutComponent(String name, Component comp) + { + // Do nothing. + } + + /** + * This method is called when the rectangles need to be calculated. It + * also fixes the size of the visible component. + */ + public void calculateLayoutInfo() + { + int count = tabPane.getTabCount(); + assureRectsCreated(count); + calculateTabRects(tabPane.getTabPlacement(), count); + tabRunsDirty = false; + } + + /** + * This method calculates the size of the the JTabbedPane. + * + * @param minimum Whether the JTabbedPane will try to be as small as it + * can. + * + * @return The desired size of the JTabbedPane. + */ + protected Dimension calculateSize(boolean minimum) + { + int tabPlacement = tabPane.getTabPlacement(); + + int width = 0; + int height = 0; + Component c; + Dimension dims; + + // Find out the minimum/preferred size to display the largest child + // of the tabbed pane. + int count = tabPane.getTabCount(); + for (int i = 0; i < count; i++) + { + c = tabPane.getComponentAt(i); + if (c == null) + continue; + dims = minimum ? c.getMinimumSize() : c.getPreferredSize(); + if (dims != null) + { + height = Math.max(height, dims.height); + width = Math.max(width, dims.width); + } + } + + Insets tabAreaInsets = getTabAreaInsets(tabPlacement); + if (tabPlacement == SwingConstants.TOP + || tabPlacement == SwingConstants.BOTTOM) + { + width = Math.max(calculateMaxTabWidth(tabPlacement), width); + + height += preferredTabAreaHeight(tabPlacement, + width - tabAreaInsets.left + - tabAreaInsets.right); + } + else + { + height = Math.max(calculateMaxTabHeight(tabPlacement), height); + + width += preferredTabAreaWidth(tabPlacement, + height - tabAreaInsets.top + - tabAreaInsets.bottom); + } + + Insets tabPaneInsets = tabPane.getInsets(); + return new Dimension(width + tabPaneInsets.left + tabPaneInsets.right, + height + tabPaneInsets.top + tabPaneInsets.bottom); + } + + // if tab placement is LEFT OR RIGHT, they share width. + // if tab placement is TOP OR BOTTOM, they share height + // PRE STEP: finds the default sizes for the labels as well as their + // locations. + // AND where they will be placed within the run system. + // 1. calls normalizeTab Runs. + // 2. calls rotate tab runs. + // 3. pads the tab runs. + // 4. pads the selected tab. + + /** + * This method is called to calculate the tab rectangles. This method + * will calculate the size and position of all rectangles (taking into + * account which ones should be in which tab run). It will pad them and + * normalize them as necessary. + * + * @param tabPlacement The JTabbedPane's tab placement. + * @param tabCount The run the current selection is in. + */ + protected void calculateTabRects(int tabPlacement, int tabCount) + { + Insets insets = tabPane.getInsets(); + Insets tabAreaInsets = getTabAreaInsets(tabPlacement); + Dimension size = tabPane.getSize(); + + // The coordinates of the upper left corner of the tab area. + int x; + int y; + // The location at which the runs must be broken. + int breakAt; + + // Calculate the bounds for the tab area. + switch (tabPlacement) + { + case LEFT: + maxTabWidth = calculateMaxTabWidth(tabPlacement); + x = insets.left + tabAreaInsets.left; + y = insets.top + tabAreaInsets.top; + breakAt = size.height - (insets.bottom + tabAreaInsets.bottom); + break; + case RIGHT: + maxTabWidth = calculateMaxTabWidth(tabPlacement); + x = size.width - (insets.right + tabAreaInsets.right) + - maxTabWidth - 1; + y = insets.top + tabAreaInsets.top; + breakAt = size.height - (insets.bottom + tabAreaInsets.bottom); + break; + case BOTTOM: + maxTabHeight = calculateMaxTabHeight(tabPlacement); + x = insets.left + tabAreaInsets.left; + y = size.height - (insets.bottom + tabAreaInsets.bottom) + - maxTabHeight - 1; + breakAt = size.width - (insets.right + tabAreaInsets.right); + break; + case TOP: + default: + maxTabHeight = calculateMaxTabHeight(tabPlacement); + x = insets.left + tabAreaInsets.left; + y = insets.top + tabAreaInsets.top; + breakAt = size.width - (insets.right + tabAreaInsets.right); + break; + } + + if (tabCount == 0) + return; + + FontMetrics fm = getFontMetrics(); + runCount = 0; + selectedRun = -1; + int selectedIndex = tabPane.getSelectedIndex(); + if (selectedIndex < 0) + selectedIndex = 0; + + Rectangle rect; + + // Go through all the tabs and build the tab runs. + if (tabPlacement == SwingConstants.TOP + || tabPlacement == SwingConstants.BOTTOM) + { + for (int i = 0; i < tabCount; i++) + { + rect = rects[i]; + if (i > 0) + { + rect.x = rects[i - 1].x + rects[i - 1].width; + } + else + { + tabRuns[0] = 0; + runCount = 1; + maxTabWidth = 0; + rect.x = x; + } + rect.width = calculateTabWidth(tabPlacement, i, fm); + maxTabWidth = Math.max(maxTabWidth, rect.width); + + if (rect.x != 2 + insets.left && rect.x + rect.width > breakAt) + { + if (runCount > tabRuns.length - 1) + expandTabRunsArray(); + tabRuns[runCount] = i; + runCount++; + rect.x = x; + } + + rect.y = y; + rect.height = maxTabHeight; + if (i == selectedIndex) + selectedRun = runCount - 1; + } + } + else + { + for (int i = 0; i < tabCount; i++) + { + rect = rects[i]; + if (i > 0) + { + rect.y = rects[i - 1].y + rects[i - 1].height; + } + else + { + tabRuns[0] = 0; + runCount = 1; + maxTabHeight = 0; + rect.y = y; + } + rect.height = calculateTabHeight(tabPlacement, i, + fm.getHeight()); + maxTabHeight = Math.max(maxTabHeight, rect.height); + + if (rect.y != 2 + insets.top && rect.y + rect.height > breakAt) + { + if (runCount > tabRuns.length - 1) + expandTabRunsArray(); + tabRuns[runCount] = i; + runCount++; + rect.y = y; + } + + rect.x = x; + rect.width = maxTabWidth; + + if (i == selectedIndex) + selectedRun = runCount - 1; + } + } + + if (runCount > 1) + { + int start; + if (tabPlacement == SwingConstants.TOP + || tabPlacement == SwingConstants.BOTTOM) + start = x; + else + start = y; + normalizeTabRuns(tabPlacement, tabCount, start, breakAt); + selectedRun = getRunForTab(tabCount, selectedIndex); + if (shouldRotateTabRuns(tabPlacement)) + { + rotateTabRuns(tabPlacement, selectedRun); + } + } + + // Suppress padding if we have only one tab run. + if (runCount == 1) + return; + + // Pad the runs. + int tabRunOverlay = getTabRunOverlay(tabPlacement); + for (int i = runCount - 1; i >= 0; --i) + { + int start = tabRuns[i]; + int nextIndex; + if (i == runCount - 1) + nextIndex = 0; + else + nextIndex = i + 1; + int next = tabRuns[nextIndex]; + int end = next != 0 ? next - 1 : tabCount - 1; + if (tabPlacement == SwingConstants.TOP + || tabPlacement == SwingConstants.BOTTOM) + { + for (int j = start; j <= end; ++j) + { + rect = rects[j]; + rect.y = y; + rect.x += getTabRunIndent(tabPlacement, i); + } + if (shouldPadTabRun(tabPlacement, i)) + { + padTabRun(tabPlacement, start, end, breakAt); + } + if (tabPlacement == BOTTOM) + y -= maxTabHeight - tabRunOverlay; + else + y += maxTabHeight - tabRunOverlay; + } + else + { + for (int j = start; j <= end; ++j) + { + rect = rects[j]; + rect.x = x; + rect.y += getTabRunIndent(tabPlacement, i); + } + if (shouldPadTabRun(tabPlacement, i)) + { + padTabRun(tabPlacement, start, end, breakAt); + } + if (tabPlacement == RIGHT) + x -= maxTabWidth - tabRunOverlay; + else + x += maxTabWidth - tabRunOverlay; + + } + } + padSelectedTab(tabPlacement, selectedIndex); + } + + /** + * This method is called when the JTabbedPane is laid out in + * WRAP_TAB_LAYOUT. It calls calculateLayoutInfo to find the positions + * of all its components. + * + * @param parent The Container to lay out. + */ + public void layoutContainer(Container parent) + { + calculateLayoutInfo(); + + int tabPlacement = tabPane.getTabPlacement(); + Insets insets = tabPane.getInsets(); + + int selectedIndex = tabPane.getSelectedIndex(); + + Component selectedComponent = null; + if (selectedIndex >= 0) + selectedComponent = tabPane.getComponentAt(selectedIndex); + // The RI doesn't seem to change the component if the new selected + // component == null. This is probably so that applications can add + // one single component for every tab. + if (selectedComponent != null) + { + setVisibleComponent(selectedComponent); + } + + int childCount = tabPane.getComponentCount(); + if (childCount > 0) + { + int compX; + int compY; + int tabAreaWidth = 0; + int tabAreaHeight = 0; + switch (tabPlacement) + { + case LEFT: + tabAreaWidth = calculateTabAreaWidth(tabPlacement, runCount, + maxTabWidth); + compX = tabAreaWidth + insets.left + contentBorderInsets.left; + compY = insets.top + contentBorderInsets.top; + break; + case RIGHT: + tabAreaWidth = calculateTabAreaWidth(tabPlacement, runCount, + maxTabWidth); + compX = insets.left + contentBorderInsets.left; + compY = insets.top + contentBorderInsets.top; + break; + case BOTTOM: + tabAreaHeight = calculateTabAreaHeight(tabPlacement, runCount, + maxTabHeight); + compX = insets.left + contentBorderInsets.left; + compY = insets.top + contentBorderInsets.top; + break; + case TOP: + default: + tabAreaHeight = calculateTabAreaHeight(tabPlacement, runCount, + maxTabHeight); + + compX = insets.left + contentBorderInsets.left; + compY = tabAreaHeight + insets.top + contentBorderInsets.top; + } + Rectangle bounds = tabPane.getBounds(); + int compWidth = bounds.width - tabAreaWidth - insets.left + - insets.right - contentBorderInsets.left + - contentBorderInsets.right; + int compHeight = bounds.height - tabAreaHeight - insets.top + - insets.bottom - contentBorderInsets.top + - contentBorderInsets.bottom; + + + for (int i = 0; i < childCount; ++i) + { + Component c = tabPane.getComponent(i); + c.setBounds(compX, compY, compWidth, compHeight); + } + } + } + + /** + * This method returns the minimum layout size for the given container. + * + * @param parent The container that is being sized. + * + * @return The minimum size. + */ + public Dimension minimumLayoutSize(Container parent) + { + return calculateSize(true); + } + + // If there is more free space in an adjacent run AND the tab + // in the run can fit in the adjacent run, move it. This method + // is not perfect, it is merely an approximation. + // If you play around with Sun's JTabbedPane, you'll see that + // it does do some pretty strange things with regards to not moving tabs + // that should be moved. + // start = the x position where the tabs will begin + // max = the maximum position of where the tabs can go to + // (tabAreaInsets.left + the width of the tab area) + + /** + * This method tries to "even out" the number of tabs in each run based on + * their widths. + * + * @param tabPlacement The JTabbedPane's tab placement. + * @param tabCount The number of tabs. + * @param start The x position where the tabs will begin. + * @param max The maximum x position where the tab can run to. + */ + protected void normalizeTabRuns(int tabPlacement, int tabCount, int start, + int max) + { + boolean horizontal = tabPlacement == TOP || tabPlacement == BOTTOM; + int currentRun = runCount - 1; + double weight = 1.25; + for (boolean adjust = true; adjust == true;) + { + int last = lastTabInRun(tabCount, currentRun); + int prevLast = lastTabInRun(tabCount, currentRun - 1); + int end; + int prevLength; + if (horizontal) + { + end = rects[last].x + rects[last].width; + prevLength = (int) (maxTabWidth * weight); + } + else + { + end = rects[last].y + rects[last].height; + prevLength = (int) (maxTabWidth * weight * 2); + } + if (max - end > prevLength) + { + tabRuns[currentRun] = prevLast; + if (horizontal) + rects[prevLast].x = start; + else + rects[prevLast].y = start; + for (int i = prevLast + 1; i <= last; i++) + { + if (horizontal) + rects[i].x = rects[i - 1].x + rects[i - 1].width; + else + rects[i].y = rects[i - 1].y + rects[i - 1].height; + } + } + else if (currentRun == runCount - 1) + adjust = false; + if (currentRun - 1 > 0) + currentRun -= 1; + else + { + // Check again, but with higher ratio to avoid + // clogging up the last run. + currentRun = runCount - 1; + weight += 0.25; + } + } + } + + /** + * This method pads the tab at the selected index by the selected tab pad + * insets (so that it looks larger). + * + * @param tabPlacement The placement of the tabs. + * @param selectedIndex The selected index. + */ + protected void padSelectedTab(int tabPlacement, int selectedIndex) + { + Insets insets = getSelectedTabPadInsets(tabPlacement); + rects[selectedIndex].x -= insets.left; + rects[selectedIndex].y -= insets.top; + rects[selectedIndex].width += insets.left + insets.right; + rects[selectedIndex].height += insets.top + insets.bottom; + } + + // If the tabs on the run don't fill the width of the window, make it + // fit now. + // start = starting index of the run + // end = last index of the run + // max = tabAreaInsets.left + width (or equivalent) + // assert start <= end. + + /** + * This method makes each tab in the run larger so that the tabs expand + * to fill the runs width/height (depending on tabPlacement). + * + * @param tabPlacement The placement of the tabs. + * @param start The index of the first tab. + * @param end The last index of the tab + * @param max The amount of space in the run (width for TOP and BOTTOM + * tabPlacement). + */ + protected void padTabRun(int tabPlacement, int start, int end, int max) + { + if (tabPlacement == SwingConstants.TOP + || tabPlacement == SwingConstants.BOTTOM) + { + int runWidth = rects[end].x + rects[end].width; + int spaceRemaining = max - runWidth; + int numTabs = end - start + 1; + + // now divvy up the space. + int spaceAllocated = spaceRemaining / numTabs; + int currX = rects[start].x; + for (int i = start; i <= end; i++) + { + rects[i].x = currX; + rects[i].width += spaceAllocated; + + currX += rects[i].width; + // This is used because since the spaceAllocated + // variable is an int, it rounds down. Sometimes, + // we don't fill an entire row, so we make it do + // so now. + + if (i == end && rects[i].x + rects[i].width != max) + rects[i].width = max - rects[i].x; + } + } + else + { + int runHeight = rects[end].y + rects[end].height; + int spaceRemaining = max - runHeight; + int numTabs = end - start + 1; + + int spaceAllocated = spaceRemaining / numTabs; + int currY = rects[start].y; + for (int i = start; i <= end; i++) + { + rects[i].y = currY; + rects[i].height += spaceAllocated; + currY += rects[i].height; + if (i == end && rects[i].y + rects[i].height != max) + rects[i].height = max - rects[i].y; + } + } + } + + /** + * This method returns the preferred layout size for the given container. + * + * @param parent The container to size. + * + * @return The preferred layout size. + */ + public Dimension preferredLayoutSize(Container parent) + { + return calculateSize(false); + } + + /** + * This method returns the preferred tab height given a tabPlacement and + * width. + * + * @param tabPlacement The JTabbedPane's tab placement. + * @param width The expected width. + * + * @return The preferred tab area height. + */ + protected int preferredTabAreaHeight(int tabPlacement, int width) + { + if (tabPane.getTabCount() == 0) + return calculateTabAreaHeight(tabPlacement, 0, 0); + + int runs = 0; + int runWidth = 0; + int tabWidth = 0; + + FontMetrics fm = getFontMetrics(); + + Insets tabAreaInsets = getTabAreaInsets(tabPlacement); + Insets insets = tabPane.getInsets(); + + // Only interested in width, this is a messed up rectangle now. + width -= tabAreaInsets.left + tabAreaInsets.right + insets.left + + insets.right; + + // The reason why we can't use runCount: + // This method is only called to calculate the size request + // for the tabbedPane. However, this size request is dependent on + // our desired width. We need to find out what the height would + // be IF we got our desired width. + for (int i = 0; i < tabPane.getTabCount(); i++) + { + tabWidth = calculateTabWidth(tabPlacement, i, fm); + if (runWidth + tabWidth > width) + { + runWidth = tabWidth; + runs++; + } + else + runWidth += tabWidth; + } + runs++; + + int maxTabHeight = calculateMaxTabHeight(tabPlacement); + int tabAreaHeight = calculateTabAreaHeight(tabPlacement, runs, + maxTabHeight); + return tabAreaHeight; + } + + /** + * This method calculates the preferred tab area width given a tab + * placement and height. + * + * @param tabPlacement The JTabbedPane's tab placement. + * @param height The expected height. + * + * @return The preferred tab area width. + */ + protected int preferredTabAreaWidth(int tabPlacement, int height) + { + if (tabPane.getTabCount() == 0) + return calculateTabAreaHeight(tabPlacement, 0, 0); + + int runs = 0; + int runHeight = 0; + int tabHeight = 0; + + FontMetrics fm = getFontMetrics(); + + Insets tabAreaInsets = getTabAreaInsets(tabPlacement); + Insets insets = tabPane.getInsets(); + + height -= tabAreaInsets.top + tabAreaInsets.bottom + insets.top + + insets.bottom; + int fontHeight = fm.getHeight(); + + for (int i = 0; i < tabPane.getTabCount(); i++) + { + tabHeight = calculateTabHeight(tabPlacement, i, fontHeight); + if (runHeight + tabHeight > height) + { + runHeight = tabHeight; + runs++; + } + else + runHeight += tabHeight; + } + runs++; + + int maxTabWidth = calculateMaxTabWidth(tabPlacement); + int tabAreaWidth = calculateTabAreaWidth(tabPlacement, runs, + maxTabWidth); + return tabAreaWidth; + } + + /** + * This method rotates the places each run in the correct place the + * tabRuns array. See the comment for tabRuns for how the runs are placed + * in the array. + * + * @param tabPlacement The JTabbedPane's tab placement. + * @param selectedRun The run the current selection is in. + */ + protected void rotateTabRuns(int tabPlacement, int selectedRun) + { + if (runCount == 1 || selectedRun == 0 || selectedRun == -1) + return; + int[] newTabRuns = new int[tabRuns.length]; + int currentRun = selectedRun; + int i = 0; + do + { + newTabRuns[i] = tabRuns[currentRun]; + currentRun = getNextTabRun(currentRun); + i++; + } + while (i < runCount); + + tabRuns = newTabRuns; + BasicTabbedPaneUI.this.selectedRun = 1; + } + + /** + * This method is called when a component is removed from the + * JTabbedPane. + * + * @param comp The component removed. + */ + public void removeLayoutComponent(Component comp) + { + // Do nothing. + } + } + + /** + * This class acts as the LayoutManager for the JTabbedPane in + * SCROLL_TAB_MODE. + */ + private class TabbedPaneScrollLayout extends TabbedPaneLayout + { + /** + * This method returns the preferred layout size for the given container. + * + * @param parent The container to calculate a size for. + * + * @return The preferred layout size. + */ + public Dimension preferredLayoutSize(Container parent) + { + return super.calculateSize(false); + } + + /** + * This method returns the minimum layout size for the given container. + * + * @param parent The container to calculate a size for. + * + * @return The minimum layout size. + */ + public Dimension minimumLayoutSize(Container parent) + { + return super.calculateSize(true); + } + + /** + * This method calculates the tab area height given a desired width. + * + * @param tabPlacement The JTabbedPane's tab placement. + * @param width The expected width. + * + * @return The tab area height given the width. + */ + protected int preferredTabAreaHeight(int tabPlacement, int width) + { + if (tabPane.getTabCount() == 0) + return calculateTabAreaHeight(tabPlacement, 0, 0); + + int runs = 1; + + int maxTabHeight = calculateMaxTabHeight(tabPlacement); + int tabAreaHeight = calculateTabAreaHeight(tabPlacement, runs, + maxTabHeight); + return tabAreaHeight; + } + + /** + * This method calculates the tab area width given a desired height. + * + * @param tabPlacement The JTabbedPane's tab placement. + * @param height The expected height. + * + * @return The tab area width given the height. + */ + protected int preferredTabAreaWidth(int tabPlacement, int height) + { + if (tabPane.getTabCount() == 0) + return calculateTabAreaHeight(tabPlacement, 0, 0); + + int runs = 1; + + int maxTabWidth = calculateMaxTabWidth(tabPlacement); + int tabAreaWidth = calculateTabAreaWidth(tabPlacement, runs, maxTabWidth); + return tabAreaWidth; + } + + /** + * This method is called to calculate the tab rectangles. This method + * will calculate the size and position of all rectangles (taking into + * account which ones should be in which tab run). It will pad them and + * normalize them as necessary. + * + * @param tabPlacement The JTabbedPane's tab placement. + * @param tabCount The number of tabs. + */ + protected void calculateTabRects(int tabPlacement, int tabCount) + { + if (tabCount == 0) + return; + + FontMetrics fm = getFontMetrics(); + SwingUtilities.calculateInnerArea(tabPane, calcRect); + Insets tabAreaInsets = getTabAreaInsets(tabPlacement); + Insets insets = tabPane.getInsets(); + if (tabPlacement == SwingConstants.TOP + || tabPlacement == SwingConstants.BOTTOM) + { + int maxHeight = calculateMaxTabHeight(tabPlacement); + calcRect.width -= tabAreaInsets.left + tabAreaInsets.right; + int width = 0; + int runWidth = tabAreaInsets.left + insets.left; + int top = insets.top + tabAreaInsets.top; + for (int i = 0; i < tabCount; i++) + { + width = calculateTabWidth(tabPlacement, i, fm); + + // The proper instances should exists because + // assureRectsCreated() was being run already. + rects[i].setBounds(runWidth, top, width, maxHeight); + + runWidth += width; + } + tabAreaRect.width = tabPane.getWidth() - insets.left - insets.right; + tabAreaRect.height = maxTabHeight + tabAreaInsets.top + + tabAreaInsets.bottom; + contentRect.width = tabAreaRect.width; + contentRect.height = tabPane.getHeight() - insets.top + - insets.bottom - tabAreaRect.height; + contentRect.x = insets.left; + tabAreaRect.x = insets.left; + if (tabPlacement == SwingConstants.BOTTOM) + { + contentRect.y = insets.top; + tabAreaRect.y = contentRect.y + contentRect.height; + } + else + { + tabAreaRect.y = insets.top; + contentRect.y = tabAreaRect.y + tabAreaRect.height; + } + } + else + { + int maxWidth = calculateMaxTabWidth(tabPlacement); + + calcRect.height -= tabAreaInsets.top + tabAreaInsets.bottom; + int height = 0; + int runHeight = tabAreaInsets.top + insets.top; + int fontHeight = fm.getHeight(); + int left = insets.left + tabAreaInsets.left; + for (int i = 0; i < tabCount; i++) + { + height = calculateTabHeight(tabPlacement, i, fontHeight); + + // The proper instances should exists because + // assureRectsCreated() was being run already. + rects[i].setBounds(left, runHeight, maxWidth, height); + runHeight += height; + } + tabAreaRect.width = maxTabWidth + tabAreaInsets.left + + tabAreaInsets.right; + tabAreaRect.height = tabPane.getHeight() - insets.top + - insets.bottom; + tabAreaRect.y = insets.top; + contentRect.width = tabPane.getWidth() - insets.left - insets.right + - tabAreaRect.width; + contentRect.height = tabAreaRect.height; + contentRect.y = insets.top; + if (tabPlacement == SwingConstants.LEFT) + { + tabAreaRect.x = insets.left; + contentRect.x = tabAreaRect.x + tabAreaRect.width; + } + else + { + contentRect.x = insets.left; + tabAreaRect.x = contentRect.x + contentRect.width; + } + } + + // Unlike the behavior in the WRAP_TAB_LAYOUT the selected + // tab is not padded specially. + } + + /** + * This method is called when the JTabbedPane is laid out in + * SCROLL_TAB_LAYOUT. It finds the position for all components in the + * JTabbedPane. + * + * @param pane The JTabbedPane to be laid out. + */ + public void layoutContainer(Container pane) + { + super.layoutContainer(pane); + int tabCount = tabPane.getTabCount(); + if (tabCount == 0) + return; + int tabPlacement = tabPane.getTabPlacement(); + + if (tabPlacement == SwingConstants.TOP + || tabPlacement == SwingConstants.BOTTOM) + { + if (tabAreaRect.x + tabAreaRect.width < rects[tabCount - 1].x + + rects[tabCount - 1].width) + { + Dimension incrDims = incrButton.getPreferredSize(); + Dimension decrDims = decrButton.getPreferredSize(); + + if (tabPlacement == SwingConstants.BOTTOM) + { + // Align scroll buttons with the bottom border of the tabbed + // pane's content area. + decrButton.setBounds(tabAreaRect.x + tabAreaRect.width + - incrDims.width - decrDims.width, + tabAreaRect.y, decrDims.width, + decrDims.height); + incrButton.setBounds(tabAreaRect.x + tabAreaRect.width + - incrDims.width, tabAreaRect.y, + incrDims.width, incrDims.height); + } + else + { + // Align scroll buttons with the top border of the tabbed + // pane's content area. + decrButton.setBounds(tabAreaRect.x + tabAreaRect.width + - incrDims.width - decrDims.width, + tabAreaRect.y + tabAreaRect.height + - decrDims.height, decrDims.width, + decrDims.height); + incrButton.setBounds(tabAreaRect.x + tabAreaRect.width + - incrDims.width, + tabAreaRect.y + tabAreaRect.height + - incrDims.height, + incrDims.width, incrDims.height); + } + + tabAreaRect.width -= decrDims.width + incrDims.width; + + updateButtons(); + + incrButton.setVisible(true); + decrButton.setVisible(true); + } + else + { + incrButton.setVisible(false); + decrButton.setVisible(false); + + currentScrollOffset = 0; + currentScrollLocation = 0; + } + } + + if (tabPlacement == SwingConstants.LEFT + || tabPlacement == SwingConstants.RIGHT) + { + if (tabAreaRect.y + tabAreaRect.height < rects[tabCount - 1].y + + rects[tabCount - 1].height) + { + Dimension incrDims = incrButton.getPreferredSize(); + Dimension decrDims = decrButton.getPreferredSize(); + + if (tabPlacement == SwingConstants.RIGHT) + { + // Align scroll buttons with the right border of the tabbed + // pane's content area. + decrButton.setBounds(tabAreaRect.x, + tabAreaRect.y + tabAreaRect.height + - incrDims.height - decrDims.height, + decrDims.width, decrDims.height); + incrButton.setBounds(tabAreaRect.x, + tabAreaRect.y + tabAreaRect.height + - incrDims.height, incrDims.width, + incrDims.height); + } + else + { + // Align scroll buttons with the left border of the tabbed + // pane's content area. + decrButton.setBounds(tabAreaRect.x + tabAreaRect.width + - decrDims.width, + tabAreaRect.y + tabAreaRect.height + - incrDims.height - decrDims.height, + decrDims.width, decrDims.height); + incrButton.setBounds(tabAreaRect.x + tabAreaRect.width + - incrDims.width, + tabAreaRect.y + tabAreaRect.height + - incrDims.height, incrDims.width, + incrDims.height); + } + + tabAreaRect.height -= decrDims.height + incrDims.height; + + incrButton.setVisible(true); + decrButton.setVisible(true); + } + else + { + incrButton.setVisible(false); + decrButton.setVisible(false); + + currentScrollOffset = 0; + currentScrollLocation = 0; + } + } + viewport.setBounds(tabAreaRect.x, tabAreaRect.y, tabAreaRect.width, + tabAreaRect.height); + + updateViewPosition(); + + viewport.repaint(); + } + } + + /** + * This class handles ChangeEvents from the JTabbedPane. + * + * @specnote Apparently this class was intended to be protected, + * but was made public by a compiler bug and is now + * public for compatibility. + */ + public class TabSelectionHandler implements ChangeListener + { + /** + * This method is called whenever a ChangeEvent is fired from the + * JTabbedPane. + * + * @param e The ChangeEvent fired. + */ + public void stateChanged(ChangeEvent e) + { + selectedRun = getRunForTab(tabPane.getTabCount(), + tabPane.getSelectedIndex()); + + if (tabPane.getTabLayoutPolicy() == JTabbedPane.WRAP_TAB_LAYOUT) + tabPane.revalidate(); + tabPane.repaint(); + } + } + + /** + * This helper class is a JPanel that fits inside the ScrollViewport. This + * panel's sole job is to paint the tab rectangles inside the viewport so + * that it's clipped correctly. + */ + private class ScrollingPanel extends JPanel + { + /** + * This is a private UI class for our panel. + */ + private class ScrollingPanelUI extends BasicPanelUI + { + /** + * This method overrides the default paint method. It paints the tab + * rectangles for the JTabbedPane in the panel. + * + * @param g The Graphics object to paint with. + * @param c The JComponent to paint. + */ + public void paint(Graphics g, JComponent c) + { + int placement = tabPane.getTabPlacement(); + g.setColor(highlight); + if (placement == SwingUtilities.TOP + || placement == SwingUtilities.BOTTOM) + g.fillRect(currentScrollOffset, 0, + tabAreaRect.width, tabAreaRect.height); + else + g.fillRect(0, currentScrollOffset, + tabAreaRect.width, tabAreaRect.height); + + paintTabArea(g, placement, tabPane.getSelectedIndex()); + } + } + + /** + * This method overrides the updateUI method. It makes the default UI for + * this ScrollingPanel to be a ScrollingPanelUI. + */ + public void updateUI() + { + setUI(new ScrollingPanelUI()); + } + } + + /** + * This is a helper class that paints the panel that paints tabs. This + * custom JViewport is used so that the tabs painted in the panel will be + * clipped. This class implements UIResource so tabs are not added when + * this objects of this class are added to the JTabbedPane. + */ + private class ScrollingViewport extends JViewport implements UIResource + { + // TODO: Maybe remove this inner class. + } + + /** + * This is a helper class that implements UIResource so it is not added as a + * tab when an object of this class is added to the JTabbedPane. + */ + private class ScrollingButton extends BasicArrowButton implements UIResource + { + /** + * Creates a ScrollingButton given the direction. + * + * @param dir The direction to point in. + */ + public ScrollingButton(int dir) + { + super(dir); + } + } + + /** The button that increments the current scroll location. + * This is package-private to avoid an accessor method. */ + transient ScrollingButton incrButton; + + /** The button that decrements the current scroll location. + * This is package-private to avoid an accessor method. */ + transient ScrollingButton decrButton; + + /** The viewport used to display the tabs. + * This is package-private to avoid an accessor method. */ + transient ScrollingViewport viewport; + + /** The panel inside the viewport that paints the tabs. + * This is package-private to avoid an accessor method. */ + transient ScrollingPanel panel; + + /** The starting visible tab in the run in SCROLL_TAB_MODE. + * This is package-private to avoid an accessor method. */ + transient int currentScrollLocation; + + transient int currentScrollOffset; + + /** A reusable rectangle. */ + protected Rectangle calcRect; + + /** An array of Rectangles keeping track of the tabs' area and position. */ + protected Rectangle[] rects; + + /** The insets around the content area. */ + protected Insets contentBorderInsets; + + /** The extra insets around the selected tab. */ + protected Insets selectedTabPadInsets; + + /** The insets around the tab area. */ + protected Insets tabAreaInsets; + + /** The insets around each and every tab. */ + protected Insets tabInsets; + + /** + * The outer bottom and right edge color for both the tab and content + * border. + */ + protected Color darkShadow; + + /** The color of the focus outline on the selected tab. */ + protected Color focus; + + /** FIXME: find a use for this. */ + protected Color highlight; + + /** The top and left edge color for both the tab and content border. */ + protected Color lightHighlight; + + /** The inner bottom and right edge color for the tab and content border. */ + protected Color shadow; + + /** The maximum tab height. */ + protected int maxTabHeight; + + /** The maximum tab width. */ + protected int maxTabWidth; + + /** The number of runs in the JTabbedPane. */ + protected int runCount; + + /** The index of the run that the selected index is in. */ + protected int selectedRun; + + /** The amount of space each run overlaps the previous by. */ + protected int tabRunOverlay; + + /** The gap between text and label */ + protected int textIconGap; + + /** This array keeps track of which tabs are in which run. + * <p>The value at index i denotes the index of the first tab in run i.</p> + * <p>If the value for any index (i > 0) is 0 then (i - 1) is the last + * run.</p> + */ + protected int[] tabRuns; + + /** + * Indicates if the layout of the tab runs is ok or not. This is package + * private to avoid a synthetic accessor method. + */ + boolean tabRunsDirty; + + /** + * This is the keystroke for moving down. + * + * @deprecated 1.3 + */ + protected KeyStroke downKey; + + /** + * This is the keystroke for moving left. + * + * @deprecated 1.3 + */ + protected KeyStroke leftKey; + + /** + * This is the keystroke for moving right. + * + * @deprecated 1.3 + */ + protected KeyStroke rightKey; + + /** + * This is the keystroke for moving up. + * + * @deprecated 1.3 + */ + protected KeyStroke upKey; + + /** The listener that listens for focus events. */ + protected FocusListener focusListener; + + /** The listener that listens for mouse events. */ + protected MouseListener mouseListener; + + /** The listener that listens for property change events. */ + protected PropertyChangeListener propertyChangeListener; + + /** The listener that listens for change events. */ + protected ChangeListener tabChangeListener; + + /** The tab pane that this UI paints. */ + protected JTabbedPane tabPane; + + /** The current layout manager for the tabPane. + * This is package-private to avoid an accessor method. */ + transient LayoutManager layoutManager; + + /** The rectangle that describes the tab area's position and size. + * This is package-private to avoid an accessor method. */ + transient Rectangle tabAreaRect; + + /** The rectangle that describes the content area's position and + * size. This is package-private to avoid an accessor method. */ + transient Rectangle contentRect; + + /** + * The index over which the mouse is currently moving. + */ + private int rolloverTab; + + /** + * Determines if tabs are painted opaque or not. This can be adjusted using + * the UIManager property 'TabbedPane.tabsOpaque'. + */ + private boolean tabsOpaque; + + /** + * The currently visible component. + */ + private Component visibleComponent; + + private Color selectedColor; + + private Rectangle tempTextRect = new Rectangle(); + + private Rectangle tempIconRect = new Rectangle(); + + /** + * Creates a new BasicTabbedPaneUI object. + */ + public BasicTabbedPaneUI() + { + super(); + rects = new Rectangle[0]; + tabRuns = new int[10]; + } + + /** + * This method creates a ScrollingButton that points in the appropriate + * direction for an increasing button. + * This is package-private to avoid an accessor method. + * + * @return The increase ScrollingButton. + */ + ScrollingButton createIncreaseButton() + { + if (incrButton == null) + incrButton = new ScrollingButton(SwingConstants.NORTH); + if (tabPane.getTabPlacement() == SwingConstants.TOP + || tabPane.getTabPlacement() == SwingConstants.BOTTOM) + incrButton.setDirection(SwingConstants.EAST); + else + incrButton.setDirection(SwingConstants.SOUTH); + return incrButton; + } + + /** + * This method creates a ScrollingButton that points in the appropriate + * direction for a decreasing button. + * This is package-private to avoid an accessor method. + * + * @return The decrease ScrollingButton. + */ + ScrollingButton createDecreaseButton() + { + if (decrButton == null) + decrButton = new ScrollingButton(SwingConstants.SOUTH); + if (tabPane.getTabPlacement() == SwingConstants.TOP + || tabPane.getTabPlacement() == SwingConstants.BOTTOM) + decrButton.setDirection(SwingConstants.WEST); + else + decrButton.setDirection(SwingConstants.NORTH); + return decrButton; + } + + /** + * This method finds the point to set the view position at given the index + * of a tab. The tab will be the first visible tab in the run. + * This is package-private to avoid an accessor method. + * + * @param index The index of the first visible tab. + * + * @return The position of the first visible tab. + */ + Point findPointForIndex(int index) + { + int tabPlacement = tabPane.getTabPlacement(); + int selectedIndex = tabPane.getSelectedIndex(); + Insets insets = getSelectedTabPadInsets(tabPlacement); + int w = 0; + int h = 0; + + if (tabPlacement == TOP || tabPlacement == BOTTOM) + { + if (index > 0) + { + w += rects[index - 1].x + rects[index - 1].width; + if (index > selectedIndex) + w -= insets.left + insets.right; + } + } + + else + { + if (index > 0) + { + h += rects[index - 1].y + rects[index - 1].height; + if (index > selectedIndex) + h -= insets.top + insets.bottom; + } + } + + Point p = new Point(w, h); + return p; + } + + /** TabbedPanes in scrolling mode should use this method to + * scroll properly to the tab given by the index argument. + * + * @param index The tab to scroll to. + * @param placement The tab's placement. + */ + final void scrollTab(int index, int placement) + { + int diff; + if (index >= 0 && tabPane.isEnabledAt(index)) + { + // If the user clicked on the last tab and that one was + // only partially visible shift the scroll offset to make + // it completely visible. + switch (placement) + { + case JTabbedPane.TOP: + case JTabbedPane.BOTTOM: + if ((diff = rects[index].x + + rects[index].width + - decrButton.getX() - currentScrollOffset) > 0) + currentScrollOffset += diff; + else if ((diff = rects[index].x - currentScrollOffset) < 0) + { + if (index == 0) + currentScrollOffset = 0; + else + currentScrollOffset += diff; + } + + currentScrollLocation = tabForCoordinate(tabPane, + currentScrollOffset, + rects[index].y); + break; + default: + if ((diff = rects[index].y + rects[index].height + - decrButton.getY() - currentScrollOffset) > 0) + currentScrollOffset += diff; + else if ((diff = rects[index].y - currentScrollOffset) < 0) + { + if (index == 0) + currentScrollOffset = 0; + else + currentScrollOffset += diff; + } + + currentScrollLocation = tabForCoordinate(tabPane, + rects[index].x, + currentScrollOffset); + } + + updateViewPosition(); + updateButtons(); + } + } + + /** Sets the enabled state of the increase and decrease button + * according to the current scrolling offset and tab pane width + * (or height in TOP/BOTTOM placement). + */ + final void updateButtons() + { + int tc = tabPane.getTabCount(); + + // The increase button should be enabled as long as the + // right/bottom border of the last tab is under the left/top + // border of the decrease button. + switch (tabPane.getTabPlacement()) + { + case JTabbedPane.BOTTOM: + case JTabbedPane.TOP: + incrButton.setEnabled(currentScrollLocation + 1 < tc + && rects[tc-1].x + rects[tc-1].width + - currentScrollOffset > decrButton.getX()); + break; + default: + incrButton.setEnabled(currentScrollLocation + 1 < tc + && rects[tc-1].y + rects[tc-1].height + - currentScrollOffset > decrButton.getY()); + } + + // The decrease button is enabled when the tab pane is scrolled in any way. + decrButton.setEnabled(currentScrollOffset > 0); + + } + + /** + * Updates the position of the scrolling viewport's view + * according to the current scroll offset. + */ + final void updateViewPosition() + { + Point p = viewport.getViewPosition(); + + // The unneeded coordinate must be set to zero + // in order to correctly handle placement changes. + switch (tabPane.getTabPlacement()) + { + case JTabbedPane.LEFT: + case JTabbedPane.RIGHT: + p.x = 0; + p.y = currentScrollOffset; + break; + default: + p.x = currentScrollOffset; + p.y = 0; + } + + viewport.setViewPosition(p); + } + + /** + * This method creates a new BasicTabbedPaneUI. + * + * @param c The JComponent to create a UI for. + * + * @return A new BasicTabbedPaneUI. + */ + public static ComponentUI createUI(JComponent c) + { + return new BasicTabbedPaneUI(); + } + + /** + * This method installs the UI for the given JComponent. + * + * @param c The JComponent to install the UI for. + */ + public void installUI(JComponent c) + { + super.installUI(c); + if (c instanceof JTabbedPane) + { + tabPane = (JTabbedPane) c; + + installComponents(); + installDefaults(); + installListeners(); + installKeyboardActions(); + + layoutManager = createLayoutManager(); + tabPane.setLayout(layoutManager); + } + } + + /** + * This method uninstalls the UI for the given JComponent. + * + * @param c The JComponent to uninstall the UI for. + */ + public void uninstallUI(JComponent c) + { + layoutManager = null; + + uninstallKeyboardActions(); + uninstallListeners(); + uninstallDefaults(); + uninstallComponents(); + + tabPane = null; + } + + /** + * This method creates the appropriate layout manager for the JTabbedPane's + * current tab layout policy. If the tab layout policy is + * SCROLL_TAB_LAYOUT, then all the associated components that need to be + * created will be done so now. + * + * @return A layout manager given the tab layout policy. + */ + protected LayoutManager createLayoutManager() + { + if (tabPane.getTabLayoutPolicy() == JTabbedPane.WRAP_TAB_LAYOUT) + return new TabbedPaneLayout(); + else + { + runCount = 1; + tabRuns[0] = 0; + + incrButton = createIncreaseButton(); + incrButton.addMouseListener(mouseListener); + + decrButton = createDecreaseButton(); + decrButton.addMouseListener(mouseListener); + decrButton.setEnabled(false); + + panel = new ScrollingPanel(); + panel.setSize(Integer.MAX_VALUE, Integer.MAX_VALUE); + panel.addMouseListener(mouseListener); + panel.addFocusListener(focusListener); + + viewport = new ScrollingViewport(); + viewport.setBackground(Color.LIGHT_GRAY); + viewport.setView(panel); + viewport.setLayout(null); + + tabPane.add(incrButton); + tabPane.add(decrButton); + tabPane.add(viewport); + + return new TabbedPaneScrollLayout(); + } + } + + /** + * This method installs components for this JTabbedPane. + */ + protected void installComponents() + { + // Nothing to be done. + } + + /** + * This method uninstalls components for this JTabbedPane. + */ + protected void uninstallComponents() + { + if (incrButton != null) + tabPane.remove(incrButton); + + if (decrButton != null) + tabPane.remove(decrButton); + + if (viewport != null) + tabPane.remove(viewport); + } + + /** + * This method installs defaults for the Look and Feel. + */ + protected void installDefaults() + { + LookAndFeel.installColorsAndFont(tabPane, "TabbedPane.background", + "TabbedPane.foreground", + "TabbedPane.font"); + tabPane.setOpaque(false); + + lightHighlight = UIManager.getColor("TabbedPane.highlight"); + highlight = UIManager.getColor("TabbedPane.light"); + + shadow = UIManager.getColor("TabbedPane.shadow"); + darkShadow = UIManager.getColor("TabbedPane.darkShadow"); + + focus = UIManager.getColor("TabbedPane.focus"); + + textIconGap = UIManager.getInt("TabbedPane.textIconGap"); + tabRunOverlay = UIManager.getInt("TabbedPane.tabRunOverlay"); + + tabInsets = UIManager.getInsets("TabbedPane.tabInsets"); + selectedTabPadInsets + = UIManager.getInsets("TabbedPane.selectedTabPadInsets"); + tabAreaInsets = UIManager.getInsets("TabbedPane.tabAreaInsets"); + contentBorderInsets + = UIManager.getInsets("TabbedPane.contentBorderInsets"); + tabsOpaque = UIManager.getBoolean("TabbedPane.tabsOpaque"); + + // Although 'TabbedPane.contentAreaColor' is not defined in the defaults + // of BasicLookAndFeel it is used by this class. + selectedColor = UIManager.getColor("TabbedPane.contentAreaColor"); + if (selectedColor == null) + selectedColor = UIManager.getColor("control"); + + calcRect = new Rectangle(); + tabRuns = new int[10]; + tabAreaRect = new Rectangle(); + contentRect = new Rectangle(); + } + + /** + * This method uninstalls defaults for the Look and Feel. + */ + protected void uninstallDefaults() + { + calcRect = null; + tabAreaRect = null; + contentRect = null; + tabRuns = null; + + tempIconRect = null; + tempTextRect = null; + + contentBorderInsets = null; + tabAreaInsets = null; + selectedTabPadInsets = null; + tabInsets = null; + + focus = null; + darkShadow = null; + shadow = null; + lightHighlight = null; + highlight = null; + + selectedColor = null; + } + + /** + * This method creates and installs the listeners for this UI. + */ + protected void installListeners() + { + mouseListener = createMouseListener(); + tabChangeListener = createChangeListener(); + propertyChangeListener = createPropertyChangeListener(); + focusListener = createFocusListener(); + + tabPane.addMouseListener(mouseListener); + tabPane.addChangeListener(tabChangeListener); + tabPane.addPropertyChangeListener(propertyChangeListener); + tabPane.addFocusListener(focusListener); + } + + /** + * This method removes and nulls the listeners for this UI. + */ + protected void uninstallListeners() + { + tabPane.removeFocusListener(focusListener); + tabPane.removePropertyChangeListener(propertyChangeListener); + tabPane.removeChangeListener(tabChangeListener); + tabPane.removeMouseListener(mouseListener); + + if (incrButton != null) + incrButton.removeMouseListener(mouseListener); + + if (decrButton != null) + decrButton.removeMouseListener(mouseListener); + + if (panel != null) + { + panel.removeMouseListener(mouseListener); + panel.removeFocusListener(focusListener); + } + + focusListener = null; + propertyChangeListener = null; + tabChangeListener = null; + mouseListener = null; + } + + /** + * This method creates a new MouseListener. + * + * @return A new MouseListener. + */ + protected MouseListener createMouseListener() + { + return new MouseHandler(); + } + + /** + * This method creates a new FocusListener. + * + * @return A new FocusListener. + */ + protected FocusListener createFocusListener() + { + return new FocusHandler(); + } + + /** + * This method creates a new ChangeListener. + * + * @return A new ChangeListener. + */ + protected ChangeListener createChangeListener() + { + return new TabSelectionHandler(); + } + + /** + * This method creates a new PropertyChangeListener. + * + * @return A new PropertyChangeListener. + */ + protected PropertyChangeListener createPropertyChangeListener() + { + return new PropertyChangeHandler(); + } + + /** + * This method installs keyboard actions for the JTabbedPane. + */ + protected void installKeyboardActions() + { + InputMap keyMap = (InputMap) UIManager.get("TabbedPane.focusInputMap"); + SwingUtilities.replaceUIInputMap(tabPane, JComponent.WHEN_FOCUSED, keyMap); + + keyMap = (InputMap) UIManager.get("TabbedPane.ancestorInputMap"); + SwingUtilities + .replaceUIInputMap(tabPane, + JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, + keyMap); + + ActionMap map = getActionMap(); + SwingUtilities.replaceUIActionMap(tabPane, map); + } + + /** + * This method uninstalls keyboard actions for the JTabbedPane. + */ + protected void uninstallKeyboardActions() + { + SwingUtilities.replaceUIActionMap(tabPane, null); + SwingUtilities.replaceUIInputMap(tabPane, JComponent.WHEN_FOCUSED, null); + SwingUtilities + .replaceUIInputMap(tabPane, + JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, + null); + } + + /** + * This method returns the minimum size of the JTabbedPane. + * + * @param c The JComponent to find a size for. + * + * @return The minimum size. + */ + public Dimension getMinimumSize(JComponent c) + { + return layoutManager.minimumLayoutSize(tabPane); + } + + /** + * This method returns the maximum size of the JTabbedPane. + * + * @param c The JComponent to find a size for. + * + * @return The maximum size. + */ + public Dimension getMaximumSize(JComponent c) + { + return new Dimension(Short.MAX_VALUE, Short.MAX_VALUE); + } + + /** + * This method paints the JTabbedPane. + * + * @param g The Graphics object to paint with. + * @param c The JComponent to paint. + */ + public void paint(Graphics g, JComponent c) + { + if (!tabPane.isValid()) + tabPane.validate(); + + if (tabPane.getTabCount() == 0) + return; + + int index = tabPane.getSelectedIndex(); + if (index < 0) + index = 0; + + int tabPlacement = tabPane.getTabPlacement(); + + // Paint the tab area only in WRAP_TAB_LAYOUT Mode from this method + // because it is done through the ScrollingViewport.paint() method + // for the SCROLL_TAB_LAYOUT mode. + if (tabPane.getTabLayoutPolicy() == JTabbedPane.WRAP_TAB_LAYOUT) + { + g.setColor(highlight); + g.fillRect(tabAreaRect.x, tabAreaRect.y, + tabAreaRect.width, tabAreaRect.height); + paintTabArea(g, tabPlacement, index); + } + + paintContentBorder(g, tabPlacement, index); + } + + /** + * This method paints the tab area. This includes painting the rectangles + * that make up the tabs. + * + * @param g The Graphics object to paint with. + * @param tabPlacement The JTabbedPane's tab placement. + * @param selectedIndex The selected index. + */ + protected void paintTabArea(Graphics g, int tabPlacement, int selectedIndex) + { + // Please note: the ordering of the painting is important. + // we WANT to paint the outermost run first and then work our way in. + + // The following drawing code works for both tab layouts. + int tabCount = tabPane.getTabCount(); + + for (int i = runCount - 1; i >= 0; --i) + { + int start = tabRuns[i]; + int next; + if (i == runCount - 1) + next = tabRuns[0]; + else + next = tabRuns[i + 1]; + int end = next != 0 ? next - 1 : tabCount - 1; + for (int j = start; j <= end; ++j) + { + if (j != selectedIndex) + { + paintTab(g, tabPlacement, rects, j, + tempIconRect, tempTextRect); + } + } + } + + // Paint selected tab in front of every other tab. + if (selectedIndex >= 0) + paintTab(g, tabPlacement, rects, selectedIndex, + tempIconRect, tempTextRect); + } + + /** + * This method paints an individual tab. + * + * @param g The Graphics object to paint with. + * @param tabPlacement The JTabbedPane's tab placement. + * @param rects The array of rectangles that keep the size and position of + * the tabs. + * @param tabIndex The tab index to paint. + * @param iconRect The rectangle to use for the icon. + * @param textRect The rectangle to use for the text. + */ + protected void paintTab(Graphics g, int tabPlacement, Rectangle[] rects, + int tabIndex, Rectangle iconRect, Rectangle textRect) + { + Rectangle rect = rects[tabIndex]; + boolean isSelected = tabIndex == tabPane.getSelectedIndex(); + // Paint background if necessary. + if (tabsOpaque || tabPane.isOpaque()) + { + paintTabBackground(g, tabPlacement, tabIndex, rect.x, rect.y, + rect.width, rect.height, isSelected); + } + + // Paint border. + paintTabBorder(g, tabPlacement, tabIndex, rect.x, rect.y, rect.width, + rect.height, isSelected); + + // Layout label. + FontMetrics fm = getFontMetrics(); + Icon icon = getIconForTab(tabIndex); + String title = tabPane.getTitleAt(tabIndex); + layoutLabel(tabPlacement, fm, tabIndex, title, icon, rect, iconRect, + textRect, isSelected); + // Paint the text. + paintText(g, tabPlacement, tabPane.getFont(), fm, tabIndex, title, + textRect, isSelected); + + // Paint icon if necessary. + paintIcon(g, tabPlacement, tabIndex, icon, iconRect, isSelected); + + // Paint focus indicator. + paintFocusIndicator(g, tabPlacement, rects, tabIndex, iconRect, textRect, + isSelected); + } + + /** + * This method lays out the tab and finds the location to paint the icon + * and text. + * + * @param tabPlacement The JTabbedPane's tab placement. + * @param metrics The font metrics for the font to paint with. + * @param tabIndex The tab index to paint. + * @param title The string painted. + * @param icon The icon painted. + * @param tabRect The tab bounds. + * @param iconRect The calculated icon bounds. + * @param textRect The calculated text bounds. + * @param isSelected Whether this tab is selected. + */ + protected void layoutLabel(int tabPlacement, FontMetrics metrics, + int tabIndex, String title, Icon icon, + Rectangle tabRect, Rectangle iconRect, + Rectangle textRect, boolean isSelected) + { + // Reset the icon and text rectangles, as the result is not specified + // when the locations are not (0,0). + textRect.x = 0; + textRect.y = 0; + textRect.width = 0; + textRect.height = 0; + iconRect.x = 0; + iconRect.y = 0; + iconRect.width = 0; + iconRect.height = 0; + SwingUtilities.layoutCompoundLabel(tabPane, metrics, title, icon, + SwingConstants.CENTER, + SwingConstants.CENTER, + SwingConstants.CENTER, + SwingConstants.RIGHT, tabRect, + iconRect, textRect, textIconGap); + + int shiftX = getTabLabelShiftX(tabPlacement, tabIndex, isSelected); + int shiftY = getTabLabelShiftY(tabPlacement, tabIndex, isSelected); + + iconRect.x += shiftX; + iconRect.y += shiftY; + + textRect.x += shiftX; + textRect.y += shiftY; + } + + /** + * This method paints the icon. + * + * @param g The Graphics object to paint. + * @param tabPlacement The JTabbedPane's tab placement. + * @param tabIndex The tab index to paint. + * @param icon The icon to paint. + * @param iconRect The bounds of the icon. + * @param isSelected Whether this tab is selected. + */ + protected void paintIcon(Graphics g, int tabPlacement, int tabIndex, + Icon icon, Rectangle iconRect, boolean isSelected) + { + if (icon != null) + icon.paintIcon(tabPane, g, iconRect.x, iconRect.y); + } + + /** + * This method paints the text for the given tab. + * + * @param g The Graphics object to paint with. + * @param tabPlacement The JTabbedPane's tab placement. + * @param font The font to paint with. + * @param metrics The fontmetrics of the given font. + * @param tabIndex The tab index. + * @param title The string to paint. + * @param textRect The bounds of the string. + * @param isSelected Whether this tab is selected. + */ + protected void paintText(Graphics g, int tabPlacement, Font font, + FontMetrics metrics, int tabIndex, String title, + Rectangle textRect, boolean isSelected) + { + g.setFont(font); + View textView = getTextViewForTab(tabIndex); + if (textView != null) + { + textView.paint(g, textRect); + return; + } + + int ascent = metrics.getAscent(); + + int mnemIndex = tabPane.getDisplayedMnemonicIndexAt(tabIndex); + if (tabPane.isEnabled() && tabPane.isEnabledAt(tabIndex)) + { + Color fg = tabPane.getForegroundAt(tabIndex); + if (isSelected && (fg instanceof UIResource)) + { + Color selectionForeground = + UIManager.getColor("TabbedPane.selectionForeground"); + if (selectionForeground != null) + fg = selectionForeground; + } + g.setColor(fg); + + if (mnemIndex != -1) + BasicGraphicsUtils.drawStringUnderlineCharAt(g, title, mnemIndex, + textRect.x, + textRect.y + ascent); + else + g.drawString(title, textRect.x, textRect.y + ascent); + } + else + { + Color bg = tabPane.getBackgroundAt(tabIndex); + g.setColor(bg.brighter()); + if (mnemIndex != -1) + BasicGraphicsUtils.drawStringUnderlineCharAt(g, title, mnemIndex, + textRect.x, textRect.y + + ascent); + else + g.drawString(title, textRect.x, textRect.y + ascent); + + g.setColor(bg.darker()); + if (mnemIndex != -1) + BasicGraphicsUtils.drawStringUnderlineCharAt(g, title, mnemIndex, + textRect.x + 1, + textRect.y + 1 + + ascent); + else + g.drawString(title, textRect.x + 1, textRect.y + 1 + ascent); + } + } + + /** + * This method returns how much the label for the tab should shift in the X + * direction. + * + * @param tabPlacement The JTabbedPane's tab placement. + * @param tabIndex The tab index being painted. + * @param isSelected Whether this tab is selected. + * + * @return The amount the label should shift by in the X direction. + */ + protected int getTabLabelShiftX(int tabPlacement, int tabIndex, + boolean isSelected) + { + switch (tabPlacement) + { + default: + case SwingUtilities.TOP: + case SwingUtilities.BOTTOM: + return 1; + case SwingUtilities.LEFT: + return (isSelected) ? -1 : 1; + case SwingUtilities.RIGHT: + return (isSelected) ? 1 : -1; + } + } + + /** + * This method returns how much the label for the tab should shift in the Y + * direction. + * + * @param tabPlacement The JTabbedPane's tab placement. + * @param tabIndex The tab index being painted. + * @param isSelected Whether this tab is selected. + * + * @return The amount the label should shift by in the Y direction. + */ + protected int getTabLabelShiftY(int tabPlacement, int tabIndex, + boolean isSelected) + { + switch (tabPlacement) + { + default: + case SwingUtilities.TOP: + return (isSelected) ? -1 : 1; + case SwingUtilities.BOTTOM: + return (isSelected) ? 1 : -1; + case SwingUtilities.LEFT: + case SwingUtilities.RIGHT: + return 0; + } + } + + /** + * This method paints the focus rectangle around the selected tab. + * + * @param g The Graphics object to paint with. + * @param tabPlacement The JTabbedPane's tab placement. + * @param rects The array of rectangles keeping track of size and position. + * @param tabIndex The tab index. + * @param iconRect The icon bounds. + * @param textRect The text bounds. + * @param isSelected Whether this tab is selected. + */ + protected void paintFocusIndicator(Graphics g, int tabPlacement, + Rectangle[] rects, int tabIndex, + Rectangle iconRect, Rectangle textRect, + boolean isSelected) + { + if (tabPane.hasFocus() && isSelected) + { + Rectangle rect = rects[tabIndex]; + // The focus rectangle. + int x; + int y; + int w; + int h; + + g.setColor(focus); + switch (tabPlacement) + { + case LEFT: + x = rect.x + 3; + y = rect.y + 3; + w = rect.width - 5; + h = rect.height - 6; + break; + case RIGHT: + x = rect.x + 2; + y = rect.y + 3; + w = rect.width - 6; + h = rect.height - 5; + break; + case BOTTOM: + x = rect.x + 3; + y = rect.y + 2; + w = rect.width - 6; + h = rect.height - 5; + break; + case TOP: + default: + x = rect.x + 3; + y = rect.y + 3; + w = rect.width - 6; + h = rect.height - 5; + } + + BasicGraphicsUtils.drawDashedRect(g, x, y, w, h); + } + } + + /** + * This method paints the border for an individual tab. + * + * @param g The Graphics object to paint with. + * @param tabPlacement The JTabbedPane's tab placement. + * @param tabIndex The tab index. + * @param x The x position of the tab. + * @param y The y position of the tab. + * @param w The width of the tab. + * @param h The height of the tab. + * @param isSelected Whether the tab is selected. + */ + protected void paintTabBorder(Graphics g, int tabPlacement, int tabIndex, + int x, int y, int w, int h, boolean isSelected) + { + Color saved = g.getColor(); + + switch (tabPlacement) + { + case SwingConstants.TOP: + g.setColor(shadow); + // Inner right line. + g.drawLine(x + w - 2, y + 2, x + w - 2, y + h); + + g.setColor(darkShadow); + // Outer right line. + g.drawLine(x + w - 1, y + 2, x + w - 1, y + h); + + // Upper right corner. + g.drawLine(x + w - 2, y + 1, x + w - 1, y + 2); + + g.setColor(lightHighlight); + + // Left line. + g.drawLine(x, y + 3, x, y + h); + + // Upper line. + g.drawLine(x + 3, y, x + w - 3, y); + + // Upper left corner. + g.drawLine(x, y + 2, x + 2, y); + + break; + case SwingConstants.LEFT: + g.setColor(lightHighlight); + // Top line. + g.drawLine(x + 3, y, x + w - 1, y); + + // Top left border. + g.drawLine(x + 2, y, x, y + 2); + + // Left line. + g.drawLine(x, y + 3, x, y + h - 4); + + // Bottom left corner. + g.drawLine(x, y + h - 3, x + 1, y + h - 2); + + g.setColor(darkShadow); + // Outer bottom line. + g.drawLine(x + 2, y + h - 1, x + w - 1, y + h - 1); + + g.setColor(shadow); + // Inner bottom line. + g.drawLine(x + 2, y + h - 2, x + w - 1, y + h - 2); + + break; + case SwingConstants.BOTTOM: + g.setColor(shadow); + // Inner right line. + g.drawLine(x + w - 2, y, x + w - 2, y + h - 2); + + // Inner bottom line. + g.drawLine(x + 2, y + h - 1, x + w - 3, y + h - 1); + + g.setColor(darkShadow); + // Outer right line. + g.drawLine(x + w - 1, y, x + w - 1, y + h - 3); + + // Bottom right corner. + g.drawLine(x + w - 1, y + h - 2, x + w - 3, y + h); + + // Bottom line. + g.drawLine(x + 2, y + h, x + w - 4, y + h); + + g.setColor(lightHighlight); + // Left line. + g.drawLine(x, y, x, y + h - 3); + + // Bottom left corner. + g.drawLine(x, y + h - 2, x + 1, y + h - 1); + break; + case SwingConstants.RIGHT: + g.setColor(lightHighlight); + // Top line. + g.drawLine(x, y, x + w - 3, y); + + g.setColor(darkShadow); + // Top right corner. + g.drawLine(x + w - 2, y + 1, x + w - 1, y + 2); + + // Outer right line. + g.drawLine(x + w - 1, y + 3, x + w - 1, y + h - 3); + + // Bottom right corner. + g.drawLine(x + w - 2, y + h - 2, x + w - 3, y + h - 1); + + // Bottom line. + g.drawLine(x, y + h - 1, x + w - 4, y + h - 1); + + g.setColor(shadow); + + // Inner right line. + g.drawLine(x + w - 2, y + 2, x + w - 2, y + h - 3); + + // Inner bottom line. + g.drawLine(x, y + h - 2, x + w - 3, y + h - 2); + + break; + } + + g.setColor(saved); + } + + /** + * This method paints the background for an individual tab. + * + * @param g The Graphics object to paint with. + * @param tabPlacement The JTabbedPane's tab placement. + * @param tabIndex The tab index. + * @param x The x position of the tab. + * @param y The y position of the tab. + * @param w The width of the tab. + * @param h The height of the tab. + * @param isSelected Whether the tab is selected. + */ + protected void paintTabBackground(Graphics g, int tabPlacement, + int tabIndex, int x, int y, int w, int h, + boolean isSelected) + { + Color saved = g.getColor(); + + if (isSelected) + g.setColor(selectedColor); + else + { + Color bg = tabPane.getBackgroundAt(tabIndex); + if (bg == null) + bg = Color.LIGHT_GRAY; + g.setColor(bg); + } + + switch (tabPlacement) + { + case SwingConstants.TOP: + g.fillRect(x + 1, y + 1, w - 1, h - 1); + break; + case SwingConstants.BOTTOM: + g.fillRect(x, y, w - 1, h - 1); + break; + case SwingConstants.LEFT: + g.fillRect(x + 1, y + 1, w - 1, h - 2); + break; + case SwingConstants.RIGHT: + g.fillRect(x, y + 1, w - 1, h - 2); + break; + } + + g.setColor(saved); + } + + /** + * This method paints the border around the content area. + * + * @param g The Graphics object to paint with. + * @param tabPlacement The JTabbedPane's tab placement. + * @param selectedIndex The index of the selected tab. + */ + protected void paintContentBorder(Graphics g, int tabPlacement, + int selectedIndex) + { + int width = tabPane.getWidth(); + int height = tabPane.getHeight(); + Insets insets = tabPane.getInsets(); + + // Calculate coordinates of content area. + int x = insets.left; + int y = insets.top; + int w = width - insets.left - insets.right; + int h = height - insets.top - insets.bottom; + + switch (tabPlacement) + { + case LEFT: + x += calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth); + w -= x - insets.left; + break; + case RIGHT: + w -= calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth); + break; + case BOTTOM: + h -= calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight); + break; + case TOP: + default: + y += calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight); + h -= y - insets.top; + } + + // Fill background if necessary. + if (tabPane.isOpaque()) + { + Color bg = UIManager.getColor("TabbedPane.contentAreaColor"); + g.setColor(bg); + g.fillRect(x, y, w, h); + } + + // Paint border. + paintContentBorderTopEdge(g, tabPlacement, selectedIndex, x, y, w, h); + paintContentBorderLeftEdge(g, tabPlacement, selectedIndex, x, y, w, h); + paintContentBorderBottomEdge(g, tabPlacement, selectedIndex, x, y, w, h); + paintContentBorderRightEdge(g, tabPlacement, selectedIndex, x, y, w, h); + } + + /** + * This method paints the top edge of the content border. + * + * @param g The Graphics object to paint with. + * @param tabPlacement The JTabbedPane's tab placement. + * @param selectedIndex The selected tab index. + * @param x The x coordinate for the content area. + * @param y The y coordinate for the content area. + * @param w The width of the content area. + * @param h The height of the content area. + */ + protected void paintContentBorderTopEdge(Graphics g, int tabPlacement, + int selectedIndex, int x, int y, + int w, int h) + { + Color saved = g.getColor(); + g.setColor(lightHighlight); + + int startgap = rects[selectedIndex].x - currentScrollOffset; + int endgap = rects[selectedIndex].x + rects[selectedIndex].width + - currentScrollOffset; + + // Paint the highlight line with a gap if the tabs are at the top + // and the selected tab is inside the visible area. + if (tabPlacement == SwingConstants.TOP && startgap >= 0) + { + g.drawLine(x, y, startgap, y); + g.drawLine(endgap, y, x + w - 1, y); + + g.setColor(selectedColor); + g.drawLine(startgap, y, endgap - 1, y); + } + else + g.drawLine(x, y, x + w, y); + + g.setColor(selectedColor); + g.drawLine(x, y + 1, x + w - 1, y + 1); + g.drawLine(x, y + 2, x + w - 1, y + 2); + + g.setColor(saved); + } + + /** + * This method paints the left edge of the content border. + * + * @param g The Graphics object to paint with. + * @param tabPlacement The JTabbedPane's tab placement. + * @param selectedIndex The selected tab index. + * @param x The x coordinate for the content area. + * @param y The y coordinate for the content area. + * @param w The width of the content area. + * @param h The height of the content area. + */ + protected void paintContentBorderLeftEdge(Graphics g, int tabPlacement, + int selectedIndex, int x, int y, + int w, int h) + { + Color saved = g.getColor(); + g.setColor(lightHighlight); + + int startgap = rects[selectedIndex].y - currentScrollOffset; + int endgap = rects[selectedIndex].y + rects[selectedIndex].height + - currentScrollOffset; + + if (tabPlacement == SwingConstants.LEFT && startgap >= 0) + { + g.drawLine(x, y, x, startgap); + g.drawLine(x, endgap, x, y + h - 1); + + g.setColor(selectedColor); + g.drawLine(x, startgap, x, endgap - 1); + } + else + g.drawLine(x, y, x, y + h - 1); + + g.setColor(selectedColor); + g.drawLine(x + 1, y + 1, x + 1, y + h - 4); + + g.setColor(saved); + } + + /** + * This method paints the bottom edge of the content border. + * + * @param g The Graphics object to paint with. + * @param tabPlacement The JTabbedPane's tab placement. + * @param selectedIndex The selected tab index. + * @param x The x coordinate for the content area. + * @param y The y coordinate for the content area. + * @param w The width of the content area. + * @param h The height of the content area. + */ + protected void paintContentBorderBottomEdge(Graphics g, int tabPlacement, + int selectedIndex, int x, int y, + int w, int h) + { + Color saved = g.getColor(); + + int startgap = rects[selectedIndex].x - currentScrollOffset; + int endgap = rects[selectedIndex].x + rects[selectedIndex].width + - currentScrollOffset; + + if (tabPlacement == SwingConstants.BOTTOM && startgap >= 0) + { + g.setColor(shadow); + g.drawLine(x + 1, y + h - 2, startgap, y + h - 2); + g.drawLine(endgap, y + h - 2, x + w - 2, y + h - 2); + + g.setColor(darkShadow); + g.drawLine(x, y + h - 1, startgap , y + h - 1); + g.drawLine(endgap, y + h - 1, x + w - 1, y + h - 1); + + g.setColor(selectedColor); + g.drawLine(startgap, y + h - 1, endgap - 1, y + h - 1); + g.drawLine(startgap, y + h - 2, endgap - 1, y + h - 2); + } + else + { + g.setColor(shadow); + g.drawLine(x + 1, y + h - 2, x + w - 1, y + h - 2); + g.setColor(darkShadow); + g.drawLine(x, y + h - 1, x + w - 1, y + h - 1); + } + + g.setColor(selectedColor); + g.drawLine(x + 1, y + h - 3, x + w - 2, y + h - 3); + + g.setColor(saved); + } + + /** + * This method paints the right edge of the content border. + * + * @param g The Graphics object to paint with. + * @param tabPlacement The JTabbedPane's tab placement. + * @param selectedIndex The selected tab index. + * @param x The x coordinate for the content area. + * @param y The y coordinate for the content area. + * @param w The width of the content area. + * @param h The height of the content area. + */ + protected void paintContentBorderRightEdge(Graphics g, int tabPlacement, + int selectedIndex, int x, int y, + int w, int h) + { + Color saved = g.getColor(); + int startgap = rects[selectedIndex].y - currentScrollOffset; + int endgap = rects[selectedIndex].y + rects[selectedIndex].height + - currentScrollOffset; + + if (tabPlacement == SwingConstants.RIGHT && startgap >= 0) + { + g.setColor(shadow); + g.drawLine(x + w - 2, y + 1, x + w - 2, startgap); + g.drawLine(x + w - 2, endgap, x + w - 2, y + h - 2); + + g.setColor(darkShadow); + g.drawLine(x + w - 1, y, x + w - 1, startgap); + g.drawLine(x + w - 1, endgap, x + w - 1, y + h - 2); + + g.setColor(selectedColor); + g.drawLine(x + w - 2, startgap, x + w - 2, endgap - 1); + g.drawLine(x + w - 1, startgap, x + w - 1, endgap - 1); + } + else + { + g.setColor(shadow); + g.drawLine(x + w - 2, y + 1, x + w - 2, y + h - 2); + g.setColor(darkShadow); + g.drawLine(x + w - 1, y, x + w - 1, y + h - 2); + } + + g.setColor(selectedColor); + g.drawLine(x + w - 3, y + 1, x + w - 3, y + h - 4); + + g.setColor(saved); + } + + /** + * <p>This method returns the bounds of a tab for the given index + * and shifts it by the current scrolling offset if the tabbed + * pane is in scrolling tab layout mode.</p> + * + * <p>Subclassses should retrievs a tab's bounds by this method + * if they want to find out whether the tab is currently visible.</p> + * + * @param pane The JTabbedPane. + * @param i The index to look for. + * + * @return The bounds of the tab with the given index. + */ + public Rectangle getTabBounds(JTabbedPane pane, int i) + { + // Need to re-layout container if tab does not exist. + if (i >= rects.length) + layoutManager.layoutContainer(pane); + + // Properly shift coordinates if scrolling has taken + // place. + if (pane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT) + { + Rectangle r = new Rectangle(rects[i]); + + switch(pane.getTabPlacement()) + { + case SwingConstants.TOP: + case SwingConstants.BOTTOM: + r.x -= currentScrollOffset; + break; + default: + r.y -= currentScrollOffset; + } + + return r; + } + + return rects[i]; + } + + /** + * This method returns the number of runs. + * + * @param pane The JTabbedPane. + * + * @return The number of runs. + */ + public int getTabRunCount(JTabbedPane pane) + { + return runCount; + } + + /** + * This method returns the tab index given a coordinate. + * + * @param pane The JTabbedPane. + * @param x The x coordinate. + * @param y The y coordinate. + * + * @return The tab index that the coordinate lands in. + */ + public int tabForCoordinate(JTabbedPane pane, int x, int y) + { + // Note: This code is tab layout mode agnostic. + if (! tabPane.isValid()) + tabPane.validate(); + + int tabCount = tabPane.getTabCount(); + + // If the user clicked outside of any tab rect the + // selection should not change. + int index = tabPane.getSelectedIndex(); + for (int i = 0; i < tabCount; ++i) + { + if (rects[i].contains(x, y)) + { + index = i; + break; + } + } + + return index; + } + + /** + * <p>This method returns the tab bounds in the given rectangle.</p> + * + * <p>The returned rectangle will be shifted by the current scroll + * offset if the tabbed pane is in scrolling tab layout mode.</p>. + * + * @param tabIndex The index to get bounds for. + * @param dest The rectangle to store bounds in. + * + * @return The rectangle passed in. + */ + protected Rectangle getTabBounds(int tabIndex, Rectangle dest) + { + dest.setBounds(getTabBounds(tabPane, tabIndex)); + return dest; + } + + /** + * This method returns the component that is shown in the content area. + * + * @return The component that is shown in the content area. + */ + protected Component getVisibleComponent() + { + return visibleComponent; + } + + /** + * This method sets the visible component. + * + * @param component The component to be set visible. + */ + protected void setVisibleComponent(Component component) + { + // Make old component invisible. + if (visibleComponent != null && visibleComponent != component + && visibleComponent.getParent() == tabPane) + { + visibleComponent.setVisible(false); + } + + // Make new component visible. + if (component != null && ! component.isVisible()) + { + component.setVisible(true); + } + visibleComponent = component; + } + + /** + * This method assures that enough rectangles are created given the + * tabCount. The old array is copied to the new one. + * + * @param tabCount The number of tabs. + */ + protected void assureRectsCreated(int tabCount) + { + if (rects.length < tabCount) + { + Rectangle[] old = rects; + rects = new Rectangle[tabCount]; + System.arraycopy(old, 0, rects, 0, old.length); + for (int i = old.length; i < rects.length; i++) + rects[i] = new Rectangle(); + } + } + + /** + * This method expands the tabRuns array to give it more room. The old array + * is copied to the new one. + */ + protected void expandTabRunsArray() + { + // This method adds another 10 index positions to the tabRuns array. + if (tabRuns == null) + tabRuns = new int[10]; + else + { + int[] newRuns = new int[tabRuns.length + 10]; + System.arraycopy(tabRuns, 0, newRuns, 0, tabRuns.length); + tabRuns = newRuns; + } + } + + /** + * This method returns which run a particular tab belongs to. + * + * @param tabCount The number of tabs. + * @param tabIndex The tab to find. + * + * @return The tabRuns index that it belongs to. + */ + protected int getRunForTab(int tabCount, int tabIndex) + { + if (runCount == 1 && tabIndex < tabCount && tabIndex >= 0) + return 0; + for (int i = 0; i < runCount; i++) + { + int first = lastTabInRun(tabCount, getPreviousTabRun(i)) + 1; + if (first == tabCount) + first = 0; + int last = lastTabInRun(tabCount, i); + if (last >= tabIndex && first <= tabIndex) + return i; + } + return -1; + } + + /** + * This method returns the index of the last tab in a run. + * + * @param tabCount The number of tabs. + * @param run The run to check. + * + * @return The last tab in the given run. + */ + protected int lastTabInRun(int tabCount, int run) + { + int lastTab; + if (runCount == 1) + lastTab = tabCount - 1; + else + { + int nextRun; + if (run == runCount - 1) + nextRun = 0; + else + nextRun = run + 1; + + if (tabRuns[nextRun] == 0) + lastTab = tabCount - 1; + else + lastTab = tabRuns[nextRun] - 1; + } + return lastTab; + } + + /** + * This method returns the tab run overlay. + * + * @param tabPlacement The JTabbedPane's tab placement. + * + * @return The tab run overlay. + */ + protected int getTabRunOverlay(int tabPlacement) + { + return tabRunOverlay; + } + + /** + * This method returns the tab run indent. It is used in WRAP_TAB_LAYOUT and + * makes each tab run start indented by a certain amount. + * + * @param tabPlacement The JTabbedPane's tab placement. + * @param run The run to get indent for. + * + * @return The amount a run should be indented. + */ + protected int getTabRunIndent(int tabPlacement, int run) + { + return 0; + } + + /** + * This method returns whether a tab run should be padded. + * + * @param tabPlacement The JTabbedPane's tab placement. + * @param run The run to check. + * + * @return Whether the given run should be padded. + */ + protected boolean shouldPadTabRun(int tabPlacement, int run) + { + return true; + } + + /** + * This method returns whether the tab runs should be rotated. + * + * @param tabPlacement The JTabbedPane's tab placement. + * + * @return Whether runs should be rotated. + */ + protected boolean shouldRotateTabRuns(int tabPlacement) + { + return true; + } + + /** + * This method returns an icon for the tab. If the tab is disabled, it + * should return the disabledIcon. If it is enabled, then it should return + * the default icon. + * + * @param tabIndex The tab index to get an icon for. + * + * @return The icon for the tab index. + */ + protected Icon getIconForTab(int tabIndex) + { + if (tabPane.isEnabledAt(tabIndex)) + return tabPane.getIconAt(tabIndex); + else + return tabPane.getDisabledIconAt(tabIndex); + } + + /** + * This method returns a view that can paint the text for the label. + * + * @param tabIndex The tab index to get a view for. + * + * @return The view for the tab index. + */ + protected View getTextViewForTab(int tabIndex) + { + // FIXME: When the label contains HTML this should return something + // non-null. + return null; + } + + /** + * This method returns the tab height, including insets, for the given index + * and fontheight. + * + * @param tabPlacement The JTabbedPane's tab placement. + * @param tabIndex The index of the tab to calculate. + * @param fontHeight The font height. + * + * @return This tab's height. + */ + protected int calculateTabHeight(int tabPlacement, int tabIndex, + int fontHeight) + { + // FIXME: Handle HTML by using the view (see getTextViewForTab). + + int height = fontHeight; + Icon icon = getIconForTab(tabIndex); + Insets tabInsets = getTabInsets(tabPlacement, tabIndex); + if (icon != null) + height = Math.max(height, icon.getIconHeight()); + height += tabInsets.top + tabInsets.bottom + 2; + return height; + } + + /** + * This method returns the max tab height. + * + * @param tabPlacement The JTabbedPane's tab placement. + * + * @return The maximum tab height. + */ + protected int calculateMaxTabHeight(int tabPlacement) + { + maxTabHeight = 0; + + FontMetrics fm = getFontMetrics(); + int fontHeight = fm.getHeight(); + + for (int i = 0; i < tabPane.getTabCount(); i++) + maxTabHeight = Math.max(calculateTabHeight(tabPlacement, i, fontHeight), + maxTabHeight); + + return maxTabHeight; + } + + /** + * This method calculates the tab width, including insets, for the given tab + * index and font metrics. + * + * @param tabPlacement The JTabbedPane's tab placement. + * @param tabIndex The tab index to calculate for. + * @param metrics The font's metrics. + * + * @return The tab width for the given index. + */ + protected int calculateTabWidth(int tabPlacement, int tabIndex, + FontMetrics metrics) + { + Icon icon = getIconForTab(tabIndex); + Insets insets = getTabInsets(tabPlacement, tabIndex); + + int width = insets.bottom + insets.right + 3; + if (icon != null) + { + width += icon.getIconWidth() + textIconGap; + } + + View v = getTextViewForTab(tabIndex); + if (v != null) + width += v.getPreferredSpan(View.X_AXIS); + else + { + String label = tabPane.getTitleAt(tabIndex); + width += metrics.stringWidth(label); + } + return width; + } + + /** + * This method calculates the max tab width. + * + * @param tabPlacement The JTabbedPane's tab placement. + * + * @return The maximum tab width. + */ + protected int calculateMaxTabWidth(int tabPlacement) + { + maxTabWidth = 0; + + FontMetrics fm = getFontMetrics(); + + for (int i = 0; i < tabPane.getTabCount(); i++) + maxTabWidth = Math.max(calculateTabWidth(tabPlacement, i, fm), + maxTabWidth); + + return maxTabWidth; + } + + /** + * This method calculates the tab area height, including insets, for the + * given amount of runs and tab height. + * + * @param tabPlacement The JTabbedPane's tab placement. + * @param horizRunCount The number of runs. + * @param maxTabHeight The max tab height. + * + * @return The tab area height. + */ + protected int calculateTabAreaHeight(int tabPlacement, int horizRunCount, + int maxTabHeight) + { + Insets insets = getTabAreaInsets(tabPlacement); + int tabAreaHeight = horizRunCount * maxTabHeight + - (horizRunCount - 1) + * getTabRunOverlay(tabPlacement); + + tabAreaHeight += insets.top + insets.bottom; + + return tabAreaHeight; + } + + /** + * This method calculates the tab area width, including insets, for the + * given amount of runs and tab width. + * + * @param tabPlacement The JTabbedPane's tab placement. + * @param vertRunCount The number of runs. + * @param maxTabWidth The max tab width. + * + * @return The tab area width. + */ + protected int calculateTabAreaWidth(int tabPlacement, int vertRunCount, + int maxTabWidth) + { + Insets insets = getTabAreaInsets(tabPlacement); + int tabAreaWidth = vertRunCount * maxTabWidth + - (vertRunCount - 1) + * getTabRunOverlay(tabPlacement); + + tabAreaWidth += insets.left + insets.right; + + return tabAreaWidth; + } + + /** + * This method returns the tab insets appropriately rotated. + * + * @param tabPlacement The JTabbedPane's tab placement. + * @param tabIndex The tab index. + * + * @return The tab insets for the given index. + */ + protected Insets getTabInsets(int tabPlacement, int tabIndex) + { + return tabInsets; + } + + /** + * This method returns the selected tab pad insets appropriately rotated. + * + * @param tabPlacement The JTabbedPane's tab placement. + * + * @return The selected tab pad insets. + */ + protected Insets getSelectedTabPadInsets(int tabPlacement) + { + Insets target = new Insets(0, 0, 0, 0); + rotateInsets(selectedTabPadInsets, target, tabPlacement); + return target; + } + + /** + * This method returns the tab area insets appropriately rotated. + * + * @param tabPlacement The JTabbedPane's tab placement. + * + * @return The tab area insets. + */ + protected Insets getTabAreaInsets(int tabPlacement) + { + Insets target = new Insets(0, 0, 0, 0); + rotateInsets(tabAreaInsets, target, tabPlacement); + return target; + } + + /** + * This method returns the content border insets appropriately rotated. + * + * @param tabPlacement The JTabbedPane's tab placement. + * + * @return The content border insets. + */ + protected Insets getContentBorderInsets(int tabPlacement) + { + Insets target = new Insets(0, 0, 0, 0); + rotateInsets(contentBorderInsets, target, tabPlacement); + return target; + } + + /** + * This method returns the fontmetrics for the font of the JTabbedPane. + * + * @return The font metrics for the JTabbedPane. + */ + protected FontMetrics getFontMetrics() + { + FontMetrics fm = tabPane.getFontMetrics(tabPane.getFont()); + return fm; + } + + /** + * This method navigates from the selected tab into the given direction. As + * a result, a new tab will be selected (if possible). + * + * @param direction The direction to navigate in. + */ + protected void navigateSelectedTab(int direction) + { + int tabPlacement = tabPane.getTabPlacement(); + if (tabPlacement == SwingConstants.TOP + || tabPlacement == SwingConstants.BOTTOM) + { + if (direction == SwingConstants.WEST) + selectPreviousTabInRun(tabPane.getSelectedIndex()); + else if (direction == SwingConstants.EAST) + selectNextTabInRun(tabPane.getSelectedIndex()); + + else + { + int offset = getTabRunOffset(tabPlacement, tabPane.getTabCount(), + tabPane.getSelectedIndex(), + (tabPlacement == SwingConstants.TOP) + ? direction == SwingConstants.NORTH + : direction == SwingConstants.SOUTH); + selectAdjacentRunTab(tabPlacement, tabPane.getSelectedIndex(), + offset); + } + } + if (tabPlacement == SwingConstants.LEFT + || tabPlacement == SwingConstants.RIGHT) + { + if (direction == SwingConstants.NORTH) + selectPreviousTabInRun(tabPane.getSelectedIndex()); + else if (direction == SwingConstants.SOUTH) + selectNextTabInRun(tabPane.getSelectedIndex()); + else + { + int offset = getTabRunOffset(tabPlacement, tabPane.getTabCount(), + tabPane.getSelectedIndex(), + (tabPlacement == SwingConstants.LEFT) + ? direction == SwingConstants.WEST + : direction == SwingConstants.EAST); + selectAdjacentRunTab(tabPlacement, tabPane.getSelectedIndex(), + offset); + } + } + } + + /** + * This method selects the next tab in the run. + * + * @param current The current selected index. + */ + protected void selectNextTabInRun(int current) + { + current = getNextTabIndexInRun(tabPane.getTabCount(), + current); + + if (tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT) + scrollTab(current, tabPane.getTabPlacement()); + + tabPane.setSelectedIndex(current); + } + + /** + * This method selects the previous tab in the run. + * + * @param current The current selected index. + */ + protected void selectPreviousTabInRun(int current) + { + current = getPreviousTabIndexInRun(tabPane.getTabCount(), + current); + + if (tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT) + scrollTab(current, tabPane.getTabPlacement()); + + tabPane.setSelectedIndex(current); + } + + /** + * This method selects the next tab (regardless of runs). + * + * @param current The current selected index. + */ + protected void selectNextTab(int current) + { + current = getNextTabIndex(current); + + if (tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT) + scrollTab(current, tabPane.getTabPlacement()); + + tabPane.setSelectedIndex(current); + } + + /** + * This method selects the previous tab (regardless of runs). + * + * @param current The current selected index. + */ + protected void selectPreviousTab(int current) + { + current = getPreviousTabIndex(current); + + if (tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT) + scrollTab(current, tabPane.getTabPlacement()); + + tabPane.setSelectedIndex(current); + } + + /** + * This method selects the correct tab given an offset from the current tab + * index. If the tab placement is TOP or BOTTOM, the offset will be in the + * y direction, otherwise, it will be in the x direction. A new coordinate + * will be found by adding the offset to the current location of the tab. + * The tab that the new location will be selected. + * + * @param tabPlacement The JTabbedPane's tab placement. + * @param tabIndex The tab to start from. + * @param offset The coordinate offset. + */ + protected void selectAdjacentRunTab(int tabPlacement, int tabIndex, + int offset) + { + int x = rects[tabIndex].x + rects[tabIndex].width / 2; + int y = rects[tabIndex].y + rects[tabIndex].height / 2; + + switch (tabPlacement) + { + case SwingConstants.TOP: + case SwingConstants.BOTTOM: + y += offset; + break; + case SwingConstants.RIGHT: + case SwingConstants.LEFT: + x += offset; + break; + } + + int index = tabForCoordinate(tabPane, x, y); + if (index != -1) + { + if (tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT) + scrollTab(index, tabPlacement); + tabPane.setSelectedIndex(index); + } + } + + // This method is called when you press up/down to cycle through tab runs. + // it returns the distance (between the two runs' x/y position. + // where one run is the current selected run and the other run is the run in the + // direction of the scroll (dictated by the forward flag) + // the offset is an absolute value of the difference + + /** + * This method calculates the offset distance for use in + * selectAdjacentRunTab. The offset returned will be a difference in the y + * coordinate between the run in the desired direction and the current run + * (for tabPlacement in TOP or BOTTOM). Use x coordinate for LEFT and + * RIGHT. + * + * @param tabPlacement The JTabbedPane's tab placement. + * @param tabCount The number of tabs. + * @param tabIndex The starting index. + * @param forward If forward, the run in the desired direction will be the + * next run. + * + * @return The offset between the two runs. + */ + protected int getTabRunOffset(int tabPlacement, int tabCount, int tabIndex, + boolean forward) + { + int currRun = getRunForTab(tabCount, tabIndex); + int offset; + int nextRun = forward ? getNextTabRun(currRun) : getPreviousTabRun(currRun); + if (tabPlacement == SwingConstants.TOP + || tabPlacement == SwingConstants.BOTTOM) + offset = rects[lastTabInRun(tabCount, nextRun)].y + - rects[lastTabInRun(tabCount, currRun)].y; + else + offset = rects[lastTabInRun(tabCount, nextRun)].x + - rects[lastTabInRun(tabCount, currRun)].x; + + return offset; + } + + /** + * This method returns the previous tab index. + * + * @param base The index to start from. + * + * @return The previous tab index. + */ + protected int getPreviousTabIndex(int base) + { + base--; + if (base < 0) + return tabPane.getTabCount() - 1; + return base; + } + + /** + * This method returns the next tab index. + * + * @param base The index to start from. + * + * @return The next tab index. + */ + protected int getNextTabIndex(int base) + { + base++; + if (base == tabPane.getTabCount()) + return 0; + return base; + } + + /** + * This method returns the next tab index in the run. If the next index is + * out of this run, it will return the starting tab index for the run. + * + * @param tabCount The number of tabs. + * @param base The index to start from. + * + * @return The next tab index in the run. + */ + protected int getNextTabIndexInRun(int tabCount, int base) + { + int index = getNextTabIndex(base); + int run = getRunForTab(tabCount, base); + if (base == lastTabInRun(tabCount, run)) + index = (run > 0) + ? lastTabInRun(tabCount, getPreviousTabRun(run)) + 1 + : 0; + + return index; + } + + /** + * This method returns the previous tab index in the run. If the previous + * index is out of this run, it will return the last index for the run. + * + * @param tabCount The number of tabs. + * @param base The index to start from. + * + * @return The previous tab index in the run. + */ + protected int getPreviousTabIndexInRun(int tabCount, int base) + { + int index = getPreviousTabIndex(base); + int run = getRunForTab(tabCount, base); + if (index == lastTabInRun(tabCount, getPreviousTabRun(run))) + index = lastTabInRun(tabCount, run); + + return index; + } + + /** + * This method returns the index of the previous run. + * + * @param baseRun The run to start from. + * + * @return The index of the previous run. + */ + protected int getPreviousTabRun(int baseRun) + { + if (getTabRunCount(tabPane) == 1) + return 1; + + int prevRun = --baseRun; + if (prevRun < 0) + prevRun = getTabRunCount(tabPane) - 1; + return prevRun; + } + + /** + * This method returns the index of the next run. + * + * @param baseRun The run to start from. + * + * @return The index of the next run. + */ + protected int getNextTabRun(int baseRun) + { + if (getTabRunCount(tabPane) == 1) + return 1; + + int nextRun = ++baseRun; + if (nextRun == getTabRunCount(tabPane)) + nextRun = 0; + return nextRun; + } + + /** + * This method rotates the insets given a direction to rotate them in. + * Target placement should be one of TOP, LEFT, BOTTOM, RIGHT. The rotated + * insets will be stored in targetInsets. Passing in TOP as the direction + * does nothing. Passing in LEFT switches top and left, right and bottom. + * Passing in BOTTOM switches top and bottom. Passing in RIGHT switches top + * for left, left for bottom, bottom for right, and right for top. + * + * @param topInsets The reference insets. + * @param targetInsets An Insets object to store the new insets. + * @param targetPlacement The rotation direction. + */ + protected static void rotateInsets(Insets topInsets, Insets targetInsets, + int targetPlacement) + { + // Sun's version will happily throw an NPE if params are null, + // so I won't check it either. + switch (targetPlacement) + { + default: + case SwingConstants.TOP: + targetInsets.top = topInsets.top; + targetInsets.left = topInsets.left; + targetInsets.right = topInsets.right; + targetInsets.bottom = topInsets.bottom; + break; + case SwingConstants.LEFT: + targetInsets.left = topInsets.top; + targetInsets.top = topInsets.left; + targetInsets.right = topInsets.bottom; + targetInsets.bottom = topInsets.right; + break; + case SwingConstants.BOTTOM: + targetInsets.top = topInsets.bottom; + targetInsets.bottom = topInsets.top; + targetInsets.left = topInsets.left; + targetInsets.right = topInsets.right; + break; + case SwingConstants.RIGHT: + targetInsets.top = topInsets.left; + targetInsets.left = topInsets.bottom; + targetInsets.bottom = topInsets.right; + targetInsets.right = topInsets.top; + break; + } + } + + ActionMap getActionMap() + { + ActionMap map = (ActionMap) UIManager.get("TabbedPane.actionMap"); + + if (map == null) // first time here + { + map = createActionMap(); + if (map != null) + UIManager.put("TabbedPane.actionMap", map); + } + return map; + } + + ActionMap createActionMap() + { + ActionMap map = new ActionMapUIResource(); + + map.put("navigatePageDown", new NavigatePageDownAction()); + map.put("navigatePageUp", new NavigatePageUpAction()); + map.put("navigateDown", + new NavigateAction("navigateDown", SwingConstants.SOUTH)); + + map.put("navigateUp", + new NavigateAction("navigateUp", SwingConstants.NORTH)); + + map.put("navigateLeft", + new NavigateAction("navigateLeft", SwingConstants.WEST)); + + map.put("navigateRight", + new NavigateAction("navigateRight", SwingConstants.EAST)); + + map.put("requestFocusForVisibleComponent", + new RequestFocusForVisibleComponentAction()); + map.put("requestFocus", new RequestFocusAction()); + + return map; + } + + /** + * Sets the tab which should be highlighted when in rollover mode. And + * <code>index</code> of <code>-1</code> means that the rollover tab + * is deselected (i.e. the mouse is outside of the tabarea). + * + * @param index the index of the tab that is under the mouse, <code>-1</code> + * for no tab + * + * @since 1.5 + */ + protected void setRolloverTab(int index) + { + rolloverTab = index; + } + + /** + * Retunrs the index of the tab over which the mouse is currently moving, + * or <code>-1</code> for no tab. + * + * @return the index of the tab over which the mouse is currently moving, + * or <code>-1</code> for no tab + * + * @since 1.5 + */ + protected int getRolloverTab() + { + return rolloverTab; + } +} diff --git a/libjava/classpath/javax/swing/plaf/basic/BasicTableHeaderUI.java b/libjava/classpath/javax/swing/plaf/basic/BasicTableHeaderUI.java new file mode 100644 index 000000000..7ff234e6d --- /dev/null +++ b/libjava/classpath/javax/swing/plaf/basic/BasicTableHeaderUI.java @@ -0,0 +1,566 @@ +/* BasicTableHeaderUI.java -- + Copyright (C) 2004 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 java.awt.Component; +import java.awt.Cursor; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Rectangle; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.MouseEvent; + +import javax.swing.CellRendererPane; +import javax.swing.JComponent; +import javax.swing.LookAndFeel; +import javax.swing.Timer; +import javax.swing.UIManager; +import javax.swing.border.Border; +import javax.swing.event.MouseInputListener; +import javax.swing.plaf.ComponentUI; +import javax.swing.plaf.TableHeaderUI; +import javax.swing.table.JTableHeader; +import javax.swing.table.TableCellRenderer; +import javax.swing.table.TableColumn; +import javax.swing.table.TableColumnModel; + +/** + * Basic pluggable look and feel interface for JTableHeader. + */ +public class BasicTableHeaderUI extends TableHeaderUI +{ + /** + * The width of the space (in both direction) around the column boundary, + * where mouse cursor changes shape into "resize" + */ + static int COLUMN_BOUNDARY_TOLERANCE = 3; + + public static ComponentUI createUI(JComponent h) + { + return new BasicTableHeaderUI(); + } + + /** + * The table header that is using this interface. + */ + protected JTableHeader header; + + /** + * The mouse input listener, responsible for mouse manipulations with + * the table header. + */ + protected MouseInputListener mouseInputListener; + + /** + * Paint the header cell. + */ + protected CellRendererPane rendererPane; + + /** + * The header cell border. + */ + private Border cellBorder; + + /** + * Original mouse cursor prior to resizing. + */ + private Cursor originalCursor; + + /** + * If not null, one of the columns is currently being dragged. + */ + Rectangle draggingHeaderRect; + + /** + * Handles column movement and rearrangement by mouse. The same instance works + * both as mouse listener and the mouse motion listner. + */ + public class MouseInputHandler + implements MouseInputListener + { + /** + * If true, the cursor is being already shown in the alternative "resize" + * shape. + */ + boolean showingResizeCursor; + + /** + * The position, from where the cursor is dragged during resizing. Double + * purpose field (absolute value during resizing and relative offset during + * column dragging). + */ + int draggingFrom = - 1; + + /** + * The number of the column being dragged. + */ + int draggingColumnNumber; + + /** + * The previous preferred width of the column. + */ + int prevPrefWidth = - 1; + + /** + * The timer to coalesce column resizing events. + */ + Timer timer; + + /** + * Returns without action, part of the MouseInputListener interface. + */ + public void mouseClicked(MouseEvent e) + { + // Nothing to do. + } + + /** + * If being in the resizing mode, handle resizing. + */ + public void mouseDragged(MouseEvent e) + { + TableColumn resizeIt = header.getResizingColumn(); + if (resizeIt != null && header.getResizingAllowed()) + { + // The timer is intialised on demand. + if (timer == null) + { + // The purpose of timer is to coalesce events. If the queue + // is free, the repaint event is fired immediately. + timer = new Timer(1, new ActionListener() + { + public void actionPerformed(ActionEvent e) + { + header.getTable().doLayout(); + } + }); + timer.setRepeats(false); + timer.setCoalesce(true); + } + resizeIt.setPreferredWidth(prevPrefWidth + e.getX() - draggingFrom); + timer.restart(); + } + else if (draggingHeaderRect != null && header.getReorderingAllowed()) + { + draggingHeaderRect.x = e.getX() + draggingFrom; + header.repaint(); + } + } + + /** + * Returns without action, part of the MouseInputListener interface. + */ + public void mouseEntered(MouseEvent e) + { + // Nothing to do. + } + + /** + * Reset drag information of the column resizing. + */ + public void mouseExited(MouseEvent e) + { + // Nothing to do. + } + + /** + * Change the mouse cursor if the mouse if above the column boundary. + */ + public void mouseMoved(MouseEvent e) + { + // When dragging, the functionality is handled by the mouseDragged. + if (e.getButton() == 0 && header.getResizingAllowed()) + { + TableColumnModel model = header.getColumnModel(); + int n = model.getColumnCount(); + if (n < 2) + // It must be at least two columns to have at least one boundary. + // Otherwise, nothing to do. + return; + + boolean onBoundary = false; + + int x = e.getX(); + int a = x - COLUMN_BOUNDARY_TOLERANCE; + int b = x + COLUMN_BOUNDARY_TOLERANCE; + + int p = 0; + + Scan: for (int i = 0; i < n - 1; i++) + { + p += model.getColumn(i).getWidth(); + + if (p >= a && p <= b) + { + TableColumn column = model.getColumn(i); + onBoundary = true; + + draggingFrom = x; + prevPrefWidth = column.getWidth(); + header.setResizingColumn(column); + break Scan; + } + } + + if (onBoundary != showingResizeCursor) + { + // Change the cursor shape, if needed. + if (onBoundary) + { + + originalCursor = header.getCursor(); + if (p < x) + header.setCursor(Cursor.getPredefinedCursor( + Cursor.W_RESIZE_CURSOR)); + else + header.setCursor(Cursor.getPredefinedCursor( + Cursor.E_RESIZE_CURSOR)); + } + else + { + header.setCursor(originalCursor); + header.setResizingColumn(null); + } + + showingResizeCursor = onBoundary; + } + } + } + + /** + * Starts the dragging/resizing procedure. + */ + public void mousePressed(MouseEvent e) + { + if (header.getResizingAllowed()) + { + TableColumn resizingColumn = header.getResizingColumn(); + if (resizingColumn != null) + { + resizingColumn.setPreferredWidth(resizingColumn.getWidth()); + return; + } + } + + if (header.getReorderingAllowed()) + { + TableColumnModel model = header.getColumnModel(); + int n = model.getColumnCount(); + if (n < 2) + // It must be at least two columns to change the column location. + return; + + boolean onBoundary = false; + + int x = e.getX(); + int p = 0; + int col = - 1; + + Scan: for (int i = 0; i < n; i++) + { + p += model.getColumn(i).getWidth(); + if (p > x) + { + col = i; + break Scan; + } + } + if (col < 0) + return; + + TableColumn dragIt = model.getColumn(col); + header.setDraggedColumn(dragIt); + + draggingFrom = (p - dragIt.getWidth()) - x; + draggingHeaderRect = new Rectangle(header.getHeaderRect(col)); + draggingColumnNumber = col; + } + } + + /** + * Set all column preferred width to the current width to prevend abrupt + * width changes during the next resize. + */ + public void mouseReleased(MouseEvent e) + { + if (header.getResizingColumn() != null && header.getResizingAllowed()) + endResizing(); + if (header.getDraggedColumn() != null && header.getReorderingAllowed()) + endDragging(e); + } + + /** + * Stop resizing session. + */ + void endResizing() + { + TableColumnModel model = header.getColumnModel(); + int n = model.getColumnCount(); + if (n > 2) + { + TableColumn c; + for (int i = 0; i < n; i++) + { + c = model.getColumn(i); + c.setPreferredWidth(c.getWidth()); + } + } + header.setResizingColumn(null); + showingResizeCursor = false; + if (timer != null) + timer.stop(); + header.setCursor(originalCursor); + } + + /** + * Stop the dragging session. + * + * @param e the "mouse release" mouse event, needed to determing the final + * location for the dragged column. + */ + void endDragging(MouseEvent e) + { + header.setDraggedColumn(null); + draggingHeaderRect = null; + + TableColumnModel model = header.getColumnModel(); + + // Find where have we dragged the column. + int x = e.getX(); + int p = 0; + + int col = model.getColumnCount() - 1; + int n = model.getColumnCount(); + + // This loop does not find the column if the mouse if out of the + // right boundary of the table header. Then we make this column the + // rightmost column. + Scan: for (int i = 0; i < n; i++) + { + p += model.getColumn(i).getWidth(); + if (p > x) + { + col = i; + break Scan; + } + } + + header.getTable().moveColumn(draggingColumnNumber, col); + } + } + + /** + * Create and return the mouse input listener. + * + * @return the mouse listener ({@link MouseInputHandler}, if not overridden. + */ + protected MouseInputListener createMouseInputListener() + { + return new MouseInputHandler(); + } + + /** + * Construct a new BasicTableHeaderUI, create mouse listeners. + */ + public BasicTableHeaderUI() + { + mouseInputListener = createMouseInputListener(); + } + + protected void installDefaults() + { + LookAndFeel.installColorsAndFont(header, "TableHeader.background", + "TableHeader.foreground", + "TableHeader.font"); + cellBorder = UIManager.getBorder("TableHeader.cellBorder"); + } + + protected void installKeyboardActions() + { + // AFAICS, the RI does nothing here. + } + + /** + * Add the mouse listener and the mouse motion listener to the table + * header. The listeners support table column resizing and rearrangement + * by mouse. + */ + protected void installListeners() + { + header.addMouseListener(mouseInputListener); + header.addMouseMotionListener(mouseInputListener); + } + + public void installUI(JComponent c) + { + header = (JTableHeader) c; + rendererPane = new CellRendererPane(); + installDefaults(); + installKeyboardActions(); + installListeners(); + } + + protected void uninstallDefaults() + { + header.setBackground(null); + header.setForeground(null); + header.setFont(null); + } + + protected void uninstallKeyboardActions() + { + // AFAICS, the RI does nothing here. + } + + /** + * Remove the previously installed listeners. + */ + protected void uninstallListeners() + { + header.removeMouseListener(mouseInputListener); + header.removeMouseMotionListener(mouseInputListener); + } + + public void uninstallUI(JComponent c) + { + uninstallListeners(); + uninstallKeyboardActions(); + uninstallDefaults(); + } + + /** + * Repaint the table header. + */ + public void paint(Graphics gfx, JComponent c) + { + TableColumnModel cmod = header.getColumnModel(); + int ncols = cmod.getColumnCount(); + if (ncols == 0) + return; + + Rectangle clip = gfx.getClipBounds(); + TableCellRenderer defaultRend = header.getDefaultRenderer(); + + for (int i = 0; i < ncols; ++i) + { + Rectangle bounds = header.getHeaderRect(i); + if (bounds.intersects(clip)) + { + Rectangle oldClip = gfx.getClipBounds(); + TableColumn col = cmod.getColumn(i); + TableCellRenderer rend = col.getHeaderRenderer(); + if (rend == null) + rend = defaultRend; + Object val = col.getHeaderValue(); + Component comp = rend.getTableCellRendererComponent(header.getTable(), + val, + false, // isSelected + false, // isFocused + -1, i); + // FIXME: The following settings should be performed in + // rend.getTableCellRendererComponent(). + comp.setFont(header.getFont()); + comp.setBackground(header.getBackground()); + comp.setForeground(header.getForeground()); + if (comp instanceof JComponent) + ((JComponent) comp).setBorder(cellBorder); + rendererPane.paintComponent(gfx, comp, header, bounds.x, bounds.y, + bounds.width, bounds.height); + } + } + + // This displays a running rectangle that is much simplier than the total + // animation, as it is seen in Sun's application. + // TODO animate the collumn dragging like in Sun's jre. + if (draggingHeaderRect != null) + { + gfx.setColor(header.getForeground()); + gfx.drawRect(draggingHeaderRect.x, draggingHeaderRect.y + 2, + draggingHeaderRect.width - 1, draggingHeaderRect.height - 6); + } + } + + /** + * Get the preferred header size. + * + * @param ignored unused + * + * @return the preferred size of the associated header. + */ + public Dimension getPreferredSize(JComponent ignored) + { + TableColumnModel cmod = header.getColumnModel(); + TableCellRenderer defaultRend = header.getDefaultRenderer(); + int ncols = cmod.getColumnCount(); + Dimension ret = new Dimension(0, 0); + int spacing = 0; + + if (header.getTable() != null + && header.getTable().getIntercellSpacing() != null) + spacing = header.getTable().getIntercellSpacing().width; + + for (int i = 0; i < ncols; ++i) + { + TableColumn col = cmod.getColumn(i); + TableCellRenderer rend = col.getHeaderRenderer(); + if (rend == null) + rend = defaultRend; + Object val = col.getHeaderValue(); + Component comp = rend.getTableCellRendererComponent(header.getTable(), + val, + false, // isSelected + false, // isFocused + -1, i); + comp.setFont(header.getFont()); + comp.setBackground(header.getBackground()); + comp.setForeground(header.getForeground()); + if (comp instanceof JComponent) + ((JComponent) comp).setBorder(cellBorder); + + Dimension d = comp.getPreferredSize(); + ret.width += spacing; + ret.height = Math.max(d.height, ret.height); + } + ret.width = cmod.getTotalColumnWidth(); + return ret; + } + + +} diff --git a/libjava/classpath/javax/swing/plaf/basic/BasicTableUI.java b/libjava/classpath/javax/swing/plaf/basic/BasicTableUI.java new file mode 100644 index 000000000..f5a4bcb67 --- /dev/null +++ b/libjava/classpath/javax/swing/plaf/basic/BasicTableUI.java @@ -0,0 +1,1410 @@ +/* BasicTableUI.java -- + Copyright (C) 2004 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 java.awt.Color; +import java.awt.Component; +import java.awt.ComponentOrientation; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.event.ActionEvent; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; +import java.awt.event.MouseEvent; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; + +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.ActionMap; +import javax.swing.CellRendererPane; +import javax.swing.DefaultCellEditor; +import javax.swing.DefaultListSelectionModel; +import javax.swing.InputMap; +import javax.swing.JComponent; +import javax.swing.JTable; +import javax.swing.ListSelectionModel; +import javax.swing.LookAndFeel; +import javax.swing.SwingUtilities; +import javax.swing.TransferHandler; +import javax.swing.UIManager; +import javax.swing.border.Border; +import javax.swing.event.ChangeEvent; +import javax.swing.event.MouseInputListener; +import javax.swing.plaf.ActionMapUIResource; +import javax.swing.plaf.ComponentUI; +import javax.swing.plaf.TableUI; +import javax.swing.table.TableCellEditor; +import javax.swing.table.TableCellRenderer; +import javax.swing.table.TableColumn; +import javax.swing.table.TableColumnModel; +import javax.swing.table.TableModel; + +public class BasicTableUI extends TableUI +{ + public static ComponentUI createUI(JComponent comp) + { + return new BasicTableUI(); + } + + protected FocusListener focusListener; + protected KeyListener keyListener; + protected MouseInputListener mouseInputListener; + protected CellRendererPane rendererPane; + protected JTable table; + + /** The normal cell border. */ + Border cellBorder; + + /** The action bound to KeyStrokes. */ + TableAction action; + + /** + * Listens for changes to the tables properties. + */ + private PropertyChangeListener propertyChangeListener; + + /** + * Handles key events for the JTable. Key events should be handled through + * the InputMap/ActionMap mechanism since JDK1.3. This class is only there + * for backwards compatibility. + * + * @author Roman Kennke (kennke@aicas.com) + */ + public class KeyHandler implements KeyListener + { + + /** + * Receives notification that a key has been pressed and released. + * Activates the editing session for the focused cell by pressing the + * character keys. + * + * @param event the key event + */ + public void keyTyped(KeyEvent event) + { + // Key events should be handled through the InputMap/ActionMap mechanism + // since JDK1.3. This class is only there for backwards compatibility. + + // Editor activation is a specific kind of response to ''any'' + // character key. Hence it is handled here. + if (!table.isEditing() && table.isEnabled()) + { + int r = table.getSelectedRow(); + int c = table.getSelectedColumn(); + if (table.isCellEditable(r, c)) + table.editCellAt(r, c); + } + } + + /** + * Receives notification that a key has been pressed. + * + * @param event the key event + */ + public void keyPressed(KeyEvent event) + { + // Key events should be handled through the InputMap/ActionMap mechanism + // since JDK1.3. This class is only there for backwards compatibility. + } + + /** + * Receives notification that a key has been released. + * + * @param event the key event + */ + public void keyReleased(KeyEvent event) + { + // Key events should be handled through the InputMap/ActionMap mechanism + // since JDK1.3. This class is only there for backwards compatibility. + } + } + + public class FocusHandler implements FocusListener + { + public void focusGained(FocusEvent e) + { + // The only thing that is affected by a focus change seems to be + // how the lead cell is painted. So we repaint this cell. + repaintLeadCell(); + } + + public void focusLost(FocusEvent e) + { + // The only thing that is affected by a focus change seems to be + // how the lead cell is painted. So we repaint this cell. + repaintLeadCell(); + } + + /** + * Repaints the lead cell in response to a focus change, to refresh + * the display of the focus indicator. + */ + private void repaintLeadCell() + { + int rowCount = table.getRowCount(); + int columnCount = table.getColumnCount(); + int rowLead = table.getSelectionModel().getLeadSelectionIndex(); + int columnLead = table.getColumnModel().getSelectionModel(). + getLeadSelectionIndex(); + if (rowLead >= 0 && rowLead < rowCount && columnLead >= 0 + && columnLead < columnCount) + { + Rectangle dirtyRect = table.getCellRect(rowLead, columnLead, false); + table.repaint(dirtyRect); + } + } + } + + public class MouseInputHandler implements MouseInputListener + { + Point begin, curr; + + private void updateSelection(boolean controlPressed) + { + // Update the rows + int lo_row = table.rowAtPoint(begin); + int hi_row = table.rowAtPoint(curr); + ListSelectionModel rowModel = table.getSelectionModel(); + if (lo_row != -1 && hi_row != -1) + { + if (controlPressed && rowModel.getSelectionMode() + != ListSelectionModel.SINGLE_SELECTION) + rowModel.addSelectionInterval(lo_row, hi_row); + else + rowModel.setSelectionInterval(lo_row, hi_row); + } + + // Update the columns + int lo_col = table.columnAtPoint(begin); + int hi_col = table.columnAtPoint(curr); + ListSelectionModel colModel = table.getColumnModel(). + getSelectionModel(); + if (lo_col != -1 && hi_col != -1) + { + if (controlPressed && colModel.getSelectionMode() != + ListSelectionModel.SINGLE_SELECTION) + colModel.addSelectionInterval(lo_col, hi_col); + else + colModel.setSelectionInterval(lo_col, hi_col); + } + } + + /** + * For the double click, start the cell editor. + */ + public void mouseClicked(MouseEvent e) + { + Point p = e.getPoint(); + int row = table.rowAtPoint(p); + int col = table.columnAtPoint(p); + if (table.isCellEditable(row, col)) + { + // If the cell editor is the default editor, we request the + // number of the required clicks from it. Otherwise, + // require two clicks (double click). + TableCellEditor editor = table.getCellEditor(row, col); + if (editor instanceof DefaultCellEditor) + { + DefaultCellEditor ce = (DefaultCellEditor) editor; + if (e.getClickCount() < ce.getClickCountToStart()) + return; + } + table.editCellAt(row, col); + } + } + + public void mouseDragged(MouseEvent e) + { + if (table.isEnabled()) + { + curr = new Point(e.getX(), e.getY()); + updateSelection(e.isControlDown()); + } + } + + public void mouseEntered(MouseEvent e) + { + // Nothing to do here. + } + + public void mouseExited(MouseEvent e) + { + // Nothing to do here. + } + + public void mouseMoved(MouseEvent e) + { + // Nothing to do here. + } + + public void mousePressed(MouseEvent e) + { + if (table.isEnabled()) + { + ListSelectionModel rowModel = table.getSelectionModel(); + ListSelectionModel colModel = table.getColumnModel().getSelectionModel(); + int rowLead = rowModel.getLeadSelectionIndex(); + int colLead = colModel.getLeadSelectionIndex(); + + begin = new Point(e.getX(), e.getY()); + curr = new Point(e.getX(), e.getY()); + //if control is pressed and the cell is already selected, deselect it + if (e.isControlDown() && table.isCellSelected( + table.rowAtPoint(begin), table.columnAtPoint(begin))) + { + table.getSelectionModel(). + removeSelectionInterval(table.rowAtPoint(begin), + table.rowAtPoint(begin)); + table.getColumnModel().getSelectionModel(). + removeSelectionInterval(table.columnAtPoint(begin), + table.columnAtPoint(begin)); + } + else + updateSelection(e.isControlDown()); + + // If we were editing, but the moved to another cell, stop editing + if (rowLead != rowModel.getLeadSelectionIndex() || + colLead != colModel.getLeadSelectionIndex()) + if (table.isEditing()) + table.editingStopped(new ChangeEvent(e)); + + // Must request focus explicitly. + table.requestFocusInWindow(); + } + } + + public void mouseReleased(MouseEvent e) + { + if (table.isEnabled()) + { + begin = null; + curr = null; + } + } + } + + /** + * Listens for changes to the model property of the JTable and adjusts some + * settings. + * + * @author Roman Kennke (kennke@aicas.com) + */ + private class PropertyChangeHandler implements PropertyChangeListener + { + /** + * Receives notification if one of the JTable's properties changes. + * + * @param ev the property change event + */ + public void propertyChange(PropertyChangeEvent ev) + { + String propName = ev.getPropertyName(); + if (propName.equals("model")) + { + ListSelectionModel rowSel = table.getSelectionModel(); + rowSel.clearSelection(); + ListSelectionModel colSel = table.getColumnModel().getSelectionModel(); + colSel.clearSelection(); + TableModel model = table.getModel(); + + // Adjust lead and anchor selection indices of the row and column + // selection models. + if (model.getRowCount() > 0) + { + rowSel.setAnchorSelectionIndex(0); + rowSel.setLeadSelectionIndex(0); + } + else + { + rowSel.setAnchorSelectionIndex(-1); + rowSel.setLeadSelectionIndex(-1); + } + if (model.getColumnCount() > 0) + { + colSel.setAnchorSelectionIndex(0); + colSel.setLeadSelectionIndex(0); + } + else + { + colSel.setAnchorSelectionIndex(-1); + colSel.setLeadSelectionIndex(-1); + } + } + } + } + + protected FocusListener createFocusListener() + { + return new FocusHandler(); + } + + protected MouseInputListener createMouseInputListener() + { + return new MouseInputHandler(); + } + + + /** + * Creates and returns a key listener for the JTable. + * + * @return a key listener for the JTable + */ + protected KeyListener createKeyListener() + { + return new KeyHandler(); + } + + /** + * Return the maximum size of the table. The maximum height is the row + * height times the number of rows. The maximum width is the sum of + * the maximum widths of each column. + * + * @param comp the component whose maximum size is being queried, + * this is ignored. + * @return a Dimension object representing the maximum size of the table, + * or null if the table has no elements. + */ + public Dimension getMaximumSize(JComponent comp) + { + int maxTotalColumnWidth = 0; + for (int i = 0; i < table.getColumnCount(); i++) + maxTotalColumnWidth += table.getColumnModel().getColumn(i).getMaxWidth(); + + return new Dimension(maxTotalColumnWidth, getHeight()); + } + + /** + * Return the minimum size of the table. The minimum height is the row + * height times the number of rows. The minimum width is the sum of + * the minimum widths of each column. + * + * @param comp the component whose minimum size is being queried, + * this is ignored. + * @return a Dimension object representing the minimum size of the table, + * or null if the table has no elements. + */ + public Dimension getMinimumSize(JComponent comp) + { + int minTotalColumnWidth = 0; + for (int i = 0; i < table.getColumnCount(); i++) + minTotalColumnWidth += table.getColumnModel().getColumn(i).getMinWidth(); + + return new Dimension(minTotalColumnWidth, getHeight()); + } + + /** + * Returns the preferred size for the table of that UI. + * + * @param comp ignored, the <code>table</code> field is used instead + * + * @return the preferred size for the table of that UI + */ + public Dimension getPreferredSize(JComponent comp) + { + int prefTotalColumnWidth = 0; + TableColumnModel tcm = table.getColumnModel(); + + for (int i = 0; i < tcm.getColumnCount(); i++) + { + TableColumn col = tcm.getColumn(i); + prefTotalColumnWidth += col.getPreferredWidth(); + } + + return new Dimension(prefTotalColumnWidth, getHeight()); + } + + /** + * Returns the table height. This helper method is used by + * {@link #getMinimumSize(JComponent)}, {@link #getPreferredSize(JComponent)} + * and {@link #getMaximumSize(JComponent)} to determine the table height. + * + * @return the table height + */ + private int getHeight() + { + int height = 0; + int rowCount = table.getRowCount(); + if (rowCount > 0 && table.getColumnCount() > 0) + { + Rectangle r = table.getCellRect(rowCount - 1, 0, true); + height = r.y + r.height; + } + return height; + } + + protected void installDefaults() + { + LookAndFeel.installColorsAndFont(table, "Table.background", + "Table.foreground", "Table.font"); + table.setGridColor(UIManager.getColor("Table.gridColor")); + table.setSelectionForeground(UIManager.getColor("Table.selectionForeground")); + table.setSelectionBackground(UIManager.getColor("Table.selectionBackground")); + table.setOpaque(true); + } + + /** + * Installs keyboard actions on the table. + */ + protected void installKeyboardActions() + { + // Install the input map. + InputMap inputMap = + (InputMap) SharedUIDefaults.get("Table.ancestorInputMap"); + SwingUtilities.replaceUIInputMap(table, + JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, + inputMap); + + // FIXME: The JDK uses a LazyActionMap for parentActionMap + SwingUtilities.replaceUIActionMap(table, getActionMap()); + + } + + /** + * Fetches the action map from the UI defaults, or create a new one + * if the action map hasn't been initialized. + * + * @return the action map + */ + private ActionMap getActionMap() + { + ActionMap am = (ActionMap) UIManager.get("Table.actionMap"); + if (am == null) + { + am = createDefaultActions(); + UIManager.getLookAndFeelDefaults().put("Table.actionMap", am); + } + return am; + } + + private ActionMap createDefaultActions() + { + ActionMapUIResource am = new ActionMapUIResource(); + Action action = new TableAction(); + + am.put("cut", TransferHandler.getCutAction()); + am.put("copy", TransferHandler.getCopyAction()); + am.put("paste", TransferHandler.getPasteAction()); + + am.put("cancel", action); + am.put("selectAll", action); + am.put("clearSelection", action); + am.put("startEditing", action); + + am.put("selectNextRow", action); + am.put("selectNextRowCell", action); + am.put("selectNextRowExtendSelection", action); + am.put("selectNextRowChangeLead", action); + + am.put("selectPreviousRow", action); + am.put("selectPreviousRowCell", action); + am.put("selectPreviousRowExtendSelection", action); + am.put("selectPreviousRowChangeLead", action); + + am.put("selectNextColumn", action); + am.put("selectNextColumnCell", action); + am.put("selectNextColumnExtendSelection", action); + am.put("selectNextColumnChangeLead", action); + + am.put("selectPreviousColumn", action); + am.put("selectPreviousColumnCell", action); + am.put("selectPreviousColumnExtendSelection", action); + am.put("selectPreviousColumnChangeLead", action); + + am.put("scrollLeftChangeSelection", action); + am.put("scrollLeftExtendSelection", action); + am.put("scrollRightChangeSelection", action); + am.put("scrollRightExtendSelection", action); + + am.put("scrollUpChangeSelection", action); + am.put("scrollUpExtendSelection", action); + am.put("scrollDownChangeSelection", action); + am.put("scrolldownExtendSelection", action); + + am.put("selectFirstColumn", action); + am.put("selectFirstColumnExtendSelection", action); + am.put("selectLastColumn", action); + am.put("selectLastColumnExtendSelection", action); + + am.put("selectFirstRow", action); + am.put("selectFirstRowExtendSelection", action); + am.put("selectLastRow", action); + am.put("selectLastRowExtendSelection", action); + + am.put("addToSelection", action); + am.put("toggleAndAnchor", action); + am.put("extendTo", action); + am.put("moveSelectionTo", action); + + return am; + } + + /** + * This class implements the actions that we want to happen + * when specific keys are pressed for the JTable. The actionPerformed + * method is called when a key that has been registered for the JTable + * is received. + */ + private static class TableAction + extends AbstractAction + { + /** + * What to do when this action is called. + * + * @param e the ActionEvent that caused this action. + */ + public void actionPerformed(ActionEvent e) + { + JTable table = (JTable) e.getSource(); + + DefaultListSelectionModel rowModel + = (DefaultListSelectionModel) table.getSelectionModel(); + DefaultListSelectionModel colModel + = (DefaultListSelectionModel) table.getColumnModel().getSelectionModel(); + + int rowLead = rowModel.getLeadSelectionIndex(); + int rowMax = table.getModel().getRowCount() - 1; + + int colLead = colModel.getLeadSelectionIndex(); + int colMax = table.getModel().getColumnCount() - 1; + + // The command with which the action has been called is stored + // in this undocumented action value. This allows us to have only + // one Action instance to serve all keyboard input for JTable. + String command = (String) getValue("__command__"); + if (command.equals("selectPreviousRowExtendSelection")) + { + rowModel.setLeadSelectionIndex(Math.max(rowLead - 1, 0)); + } + else if (command.equals("selectLastColumn")) + { + colModel.setSelectionInterval(colMax, colMax); + } + else if (command.equals("startEditing")) + { + if (table.isCellEditable(rowLead, colLead)) + table.editCellAt(rowLead, colLead); + } + else if (command.equals("selectFirstRowExtendSelection")) + { + rowModel.setLeadSelectionIndex(0); + } + else if (command.equals("selectFirstColumn")) + { + colModel.setSelectionInterval(0, 0); + } + else if (command.equals("selectFirstColumnExtendSelection")) + { + colModel.setLeadSelectionIndex(0); + } + else if (command.equals("selectLastRow")) + { + rowModel.setSelectionInterval(rowMax, rowMax); + } + else if (command.equals("selectNextRowExtendSelection")) + { + rowModel.setLeadSelectionIndex(Math.min(rowLead + 1, rowMax)); + } + else if (command.equals("selectFirstRow")) + { + rowModel.setSelectionInterval(0, 0); + } + else if (command.equals("selectNextColumnExtendSelection")) + { + colModel.setLeadSelectionIndex(Math.min(colLead + 1, colMax)); + } + else if (command.equals("selectLastColumnExtendSelection")) + { + colModel.setLeadSelectionIndex(colMax); + } + else if (command.equals("selectPreviousColumnExtendSelection")) + { + colModel.setLeadSelectionIndex(Math.max(colLead - 1, 0)); + } + else if (command.equals("selectNextRow")) + { + rowModel.setSelectionInterval(Math.min(rowLead + 1, rowMax), + Math.min(rowLead + 1, rowMax)); + } + else if (command.equals("scrollUpExtendSelection")) + { + int target; + if (rowLead == getFirstVisibleRowIndex(table)) + target = Math.max(0, rowLead - (getLastVisibleRowIndex(table) + - getFirstVisibleRowIndex(table) + 1)); + else + target = getFirstVisibleRowIndex(table); + + rowModel.setLeadSelectionIndex(target); + colModel.setLeadSelectionIndex(colLead); + } + else if (command.equals("selectPreviousRow")) + { + rowModel.setSelectionInterval(Math.max(rowLead - 1, 0), + Math.max(rowLead - 1, 0)); + } + else if (command.equals("scrollRightChangeSelection")) + { + int target; + if (colLead == getLastVisibleColumnIndex(table)) + target = Math.min(colMax, colLead + + (getLastVisibleColumnIndex(table) + - getFirstVisibleColumnIndex(table) + 1)); + else + target = getLastVisibleColumnIndex(table); + + colModel.setSelectionInterval(target, target); + rowModel.setSelectionInterval(rowLead, rowLead); + } + else if (command.equals("selectPreviousColumn")) + { + colModel.setSelectionInterval(Math.max(colLead - 1, 0), + Math.max(colLead - 1, 0)); + } + else if (command.equals("scrollLeftChangeSelection")) + { + int target; + if (colLead == getFirstVisibleColumnIndex(table)) + target = Math.max(0, colLead - (getLastVisibleColumnIndex(table) + - getFirstVisibleColumnIndex(table) + 1)); + else + target = getFirstVisibleColumnIndex(table); + + colModel.setSelectionInterval(target, target); + rowModel.setSelectionInterval(rowLead, rowLead); + } + else if (command.equals("clearSelection")) + { + table.clearSelection(); + } + else if (command.equals("cancel")) + { + // FIXME: implement other parts of "cancel" like undo-ing last + // selection. Right now it just calls editingCancelled if + // we're currently editing. + if (table.isEditing()) + table.editingCanceled(new ChangeEvent("cancel")); + } + else if (command.equals("selectNextRowCell") + || command.equals("selectPreviousRowCell") + || command.equals("selectNextColumnCell") + || command.equals("selectPreviousColumnCell")) + { + // If nothing is selected, select the first cell in the table + if (table.getSelectedRowCount() == 0 && + table.getSelectedColumnCount() == 0) + { + rowModel.setSelectionInterval(0, 0); + colModel.setSelectionInterval(0, 0); + return; + } + + // If the lead selection index isn't selected (ie a remove operation + // happened, then set the lead to the first selected cell in the + // table + if (!table.isCellSelected(rowLead, colLead)) + { + rowModel.addSelectionInterval(rowModel.getMinSelectionIndex(), + rowModel.getMinSelectionIndex()); + colModel.addSelectionInterval(colModel.getMinSelectionIndex(), + colModel.getMinSelectionIndex()); + return; + } + + // multRowsSelected and multColsSelected tell us if multiple rows or + // columns are selected, respectively + boolean multRowsSelected, multColsSelected; + multRowsSelected = table.getSelectedRowCount() > 1 && + table.getRowSelectionAllowed(); + + multColsSelected = table.getSelectedColumnCount() > 1 && + table.getColumnSelectionAllowed(); + + // If there is just one selection, select the next cell, and wrap + // when you get to the edges of the table. + if (!multColsSelected && !multRowsSelected) + { + if (command.indexOf("Column") != -1) + advanceSingleSelection(colModel, colMax, rowModel, rowMax, + command.equals("selectPreviousColumnCell")); + else + advanceSingleSelection(rowModel, rowMax, colModel, colMax, + command.equals("selectPreviousRowCell")); + return; + } + + + // rowMinSelected and rowMaxSelected are the minimum and maximum + // values respectively of selected cells in the row selection model + // Similarly for colMinSelected and colMaxSelected. + int rowMaxSelected = table.getRowSelectionAllowed() ? + rowModel.getMaxSelectionIndex() : table.getModel().getRowCount() - 1; + int rowMinSelected = table.getRowSelectionAllowed() ? + rowModel.getMinSelectionIndex() : 0; + int colMaxSelected = table.getColumnSelectionAllowed() ? + colModel.getMaxSelectionIndex() : + table.getModel().getColumnCount() - 1; + int colMinSelected = table.getColumnSelectionAllowed() ? + colModel.getMinSelectionIndex() : 0; + + // If there are multiple rows and columns selected, select the next + // cell and wrap at the edges of the selection. + if (command.indexOf("Column") != -1) + advanceMultipleSelection(table, colModel, colMinSelected, + colMaxSelected, rowModel, rowMinSelected, + rowMaxSelected, + command.equals("selectPreviousColumnCell"), + true); + + else + advanceMultipleSelection(table, rowModel, rowMinSelected, + rowMaxSelected, colModel, colMinSelected, + colMaxSelected, + command.equals("selectPreviousRowCell"), + false); + } + else if (command.equals("selectNextColumn")) + { + colModel.setSelectionInterval(Math.min(colLead + 1, colMax), + Math.min(colLead + 1, colMax)); + } + else if (command.equals("scrollLeftExtendSelection")) + { + int target; + if (colLead == getFirstVisibleColumnIndex(table)) + target = Math.max(0, colLead - (getLastVisibleColumnIndex(table) + - getFirstVisibleColumnIndex(table) + 1)); + else + target = getFirstVisibleColumnIndex(table); + + colModel.setLeadSelectionIndex(target); + rowModel.setLeadSelectionIndex(rowLead); + } + else if (command.equals("scrollDownChangeSelection")) + { + int target; + if (rowLead == getLastVisibleRowIndex(table)) + target = Math.min(rowMax, rowLead + (getLastVisibleRowIndex(table) + - getFirstVisibleRowIndex(table) + 1)); + else + target = getLastVisibleRowIndex(table); + + rowModel.setSelectionInterval(target, target); + colModel.setSelectionInterval(colLead, colLead); + } + else if (command.equals("scrollRightExtendSelection")) + { + int target; + if (colLead == getLastVisibleColumnIndex(table)) + target = Math.min(colMax, colLead + (getLastVisibleColumnIndex(table) + - getFirstVisibleColumnIndex(table) + 1)); + else + target = getLastVisibleColumnIndex(table); + + colModel.setLeadSelectionIndex(target); + rowModel.setLeadSelectionIndex(rowLead); + } + else if (command.equals("selectAll")) + { + table.selectAll(); + } + else if (command.equals("selectLastRowExtendSelection")) + { + rowModel.setLeadSelectionIndex(rowMax); + colModel.setLeadSelectionIndex(colLead); + } + else if (command.equals("scrollDownExtendSelection")) + { + int target; + if (rowLead == getLastVisibleRowIndex(table)) + target = Math.min(rowMax, rowLead + (getLastVisibleRowIndex(table) + - getFirstVisibleRowIndex(table) + 1)); + else + target = getLastVisibleRowIndex(table); + + rowModel.setLeadSelectionIndex(target); + colModel.setLeadSelectionIndex(colLead); + } + else if (command.equals("scrollUpChangeSelection")) + { + int target; + if (rowLead == getFirstVisibleRowIndex(table)) + target = Math.max(0, rowLead - (getLastVisibleRowIndex(table) + - getFirstVisibleRowIndex(table) + 1)); + else + target = getFirstVisibleRowIndex(table); + + rowModel.setSelectionInterval(target, target); + colModel.setSelectionInterval(colLead, colLead); + } + else if (command.equals("selectNextRowChangeLead")) + { + if (rowModel.getSelectionMode() + != ListSelectionModel.MULTIPLE_INTERVAL_SELECTION) + { + // just "selectNextRow" + rowModel.setSelectionInterval(Math.min(rowLead + 1, rowMax), + Math.min(rowLead + 1, rowMax)); + colModel.setSelectionInterval(colLead, colLead); + } + else + rowModel.moveLeadSelectionIndex(Math.min(rowLead + 1, rowMax)); + } + else if (command.equals("selectPreviousRowChangeLead")) + { + if (rowModel.getSelectionMode() + != ListSelectionModel.MULTIPLE_INTERVAL_SELECTION) + { + // just selectPreviousRow + rowModel.setSelectionInterval(Math.max(rowLead - 1, 0), + Math.min(rowLead - 1, 0)); + colModel.setSelectionInterval(colLead, colLead); + } + else + rowModel.moveLeadSelectionIndex(Math.max(rowLead - 1, 0)); + } + else if (command.equals("selectNextColumnChangeLead")) + { + if (colModel.getSelectionMode() + != ListSelectionModel.MULTIPLE_INTERVAL_SELECTION) + { + // just selectNextColumn + rowModel.setSelectionInterval(rowLead, rowLead); + colModel.setSelectionInterval(Math.min(colLead + 1, colMax), + Math.min(colLead + 1, colMax)); + } + else + colModel.moveLeadSelectionIndex(Math.min(colLead + 1, colMax)); + } + else if (command.equals("selectPreviousColumnChangeLead")) + { + if (colModel.getSelectionMode() + != ListSelectionModel.MULTIPLE_INTERVAL_SELECTION) + { + // just selectPreviousColumn + rowModel.setSelectionInterval(rowLead, rowLead); + colModel.setSelectionInterval(Math.max(colLead - 1, 0), + Math.max(colLead - 1, 0)); + + } + else + colModel.moveLeadSelectionIndex(Math.max(colLead - 1, 0)); + } + else if (command.equals("addToSelection")) + { + if (!table.isEditing()) + { + int oldRowAnchor = rowModel.getAnchorSelectionIndex(); + int oldColAnchor = colModel.getAnchorSelectionIndex(); + rowModel.addSelectionInterval(rowLead, rowLead); + colModel.addSelectionInterval(colLead, colLead); + rowModel.setAnchorSelectionIndex(oldRowAnchor); + colModel.setAnchorSelectionIndex(oldColAnchor); + } + } + else if (command.equals("extendTo")) + { + rowModel.setSelectionInterval(rowModel.getAnchorSelectionIndex(), + rowLead); + colModel.setSelectionInterval(colModel.getAnchorSelectionIndex(), + colLead); + } + else if (command.equals("toggleAndAnchor")) + { + if (rowModel.isSelectedIndex(rowLead)) + rowModel.removeSelectionInterval(rowLead, rowLead); + else + rowModel.addSelectionInterval(rowLead, rowLead); + + if (colModel.isSelectedIndex(colLead)) + colModel.removeSelectionInterval(colLead, colLead); + else + colModel.addSelectionInterval(colLead, colLead); + + rowModel.setAnchorSelectionIndex(rowLead); + colModel.setAnchorSelectionIndex(colLead); + } + else if (command.equals("stopEditing")) + { + table.editingStopped(new ChangeEvent(command)); + } + else + { + // If we're here that means we bound this TableAction class + // to a keyboard input but we either want to ignore that input + // or we just haven't implemented its action yet. + + // Uncomment the following line to print the names of unused bindings + // when their keys are pressed + + // System.out.println ("not implemented: "+e.getActionCommand()); + } + + // Any commands whose keyStrokes should be used by the Editor should not + // cause editing to be stopped: ie, the SPACE sends "addToSelection" but + // if the table is in editing mode, the space should not cause us to stop + // editing because it should be used by the Editor. + if (table.isEditing() && command != "startEditing" + && command != "addToSelection") + table.editingStopped(new ChangeEvent("update")); + + table.scrollRectToVisible(table.getCellRect( + rowModel.getLeadSelectionIndex(), colModel.getLeadSelectionIndex(), + false)); + } + + /** + * Returns the column index of the first visible column. + * @return the column index of the first visible column. + */ + int getFirstVisibleColumnIndex(JTable table) + { + ComponentOrientation or = table.getComponentOrientation(); + Rectangle r = table.getVisibleRect(); + if (!or.isLeftToRight()) + r.translate((int) r.getWidth() - 1, 0); + return table.columnAtPoint(r.getLocation()); + } + + /** + * Returns the column index of the last visible column. + * + */ + int getLastVisibleColumnIndex(JTable table) + { + ComponentOrientation or = table.getComponentOrientation(); + Rectangle r = table.getVisibleRect(); + if (or.isLeftToRight()) + r.translate((int) r.getWidth() - 1, 0); + return table.columnAtPoint(r.getLocation()); + } + + /** + * Returns the row index of the first visible row. + * + */ + int getFirstVisibleRowIndex(JTable table) + { + ComponentOrientation or = table.getComponentOrientation(); + Rectangle r = table.getVisibleRect(); + if (!or.isLeftToRight()) + r.translate((int) r.getWidth() - 1, 0); + return table.rowAtPoint(r.getLocation()); + } + + /** + * Returns the row index of the last visible row. + * + */ + int getLastVisibleRowIndex(JTable table) + { + ComponentOrientation or = table.getComponentOrientation(); + Rectangle r = table.getVisibleRect(); + r.translate(0, (int) r.getHeight() - 1); + if (or.isLeftToRight()) + r.translate((int) r.getWidth() - 1, 0); + // The next if makes sure that we don't return -1 simply because + // there is white space at the bottom of the table (ie, the display + // area is larger than the table) + if (table.rowAtPoint(r.getLocation()) == -1) + { + if (getFirstVisibleRowIndex(table) == -1) + return -1; + else + return table.getModel().getRowCount() - 1; + } + return table.rowAtPoint(r.getLocation()); + } + + /** + * A helper method for the key bindings. Used because the actions + * for TAB, SHIFT-TAB, ENTER, and SHIFT-ENTER are very similar. + * + * Selects the next (previous if SHIFT pressed) column for TAB, or row for + * ENTER from within the currently selected cells. + * + * @param firstModel the ListSelectionModel for columns (TAB) or + * rows (ENTER) + * @param firstMin the first selected index in firstModel + * @param firstMax the last selected index in firstModel + * @param secondModel the ListSelectionModel for rows (TAB) or + * columns (ENTER) + * @param secondMin the first selected index in secondModel + * @param secondMax the last selected index in secondModel + * @param reverse true if shift was held for the event + * @param eventIsTab true if TAB was pressed, false if ENTER pressed + */ + void advanceMultipleSelection(JTable table, ListSelectionModel firstModel, + int firstMin, + int firstMax, ListSelectionModel secondModel, + int secondMin, int secondMax, boolean reverse, + boolean eventIsTab) + { + // If eventIsTab, all the "firsts" correspond to columns, otherwise, to + // rows "seconds" correspond to the opposite + int firstLead = firstModel.getLeadSelectionIndex(); + int secondLead = secondModel.getLeadSelectionIndex(); + int numFirsts = eventIsTab ? + table.getModel().getColumnCount() : table.getModel().getRowCount(); + int numSeconds = eventIsTab ? + table.getModel().getRowCount() : table.getModel().getColumnCount(); + + // check if we have to wrap the "firsts" around, going to the other side + if ((firstLead == firstMax && !reverse) || + (reverse && firstLead == firstMin)) + { + firstModel.addSelectionInterval(reverse ? firstMax : firstMin, + reverse ? firstMax : firstMin); + + // check if we have to wrap the "seconds" + if ((secondLead == secondMax && !reverse) || + (reverse && secondLead == secondMin)) + secondModel.addSelectionInterval(reverse ? secondMax : secondMin, + reverse ? secondMax : secondMin); + + // if we're not wrapping the seconds, we have to find out where we + // are within the secondModel and advance to the next cell (or + // go back to the previous cell if reverse == true) + else + { + int[] secondsSelected; + if (eventIsTab && table.getRowSelectionAllowed() || + !eventIsTab && table.getColumnSelectionAllowed()) + secondsSelected = eventIsTab ? + table.getSelectedRows() : table.getSelectedColumns(); + else + { + // if row selection is not allowed, then the entire column gets + // selected when you click on it, so consider ALL rows selected + secondsSelected = new int[numSeconds]; + for (int i = 0; i < numSeconds; i++) + secondsSelected[i] = i; + } + + // and now find the "next" index within the model + int secondIndex = reverse ? secondsSelected.length - 1 : 0; + if (!reverse) + while (secondsSelected[secondIndex] <= secondLead) + secondIndex++; + else + while (secondsSelected[secondIndex] >= secondLead) + secondIndex--; + + // and select it - updating the lead selection index + secondModel.addSelectionInterval(secondsSelected[secondIndex], + secondsSelected[secondIndex]); + } + } + // We didn't have to wrap the firsts, so just find the "next" first + // and select it, we don't have to change "seconds" + else + { + int[] firstsSelected; + if (eventIsTab && table.getColumnSelectionAllowed() || + !eventIsTab && table.getRowSelectionAllowed()) + firstsSelected = eventIsTab ? + table.getSelectedColumns() : table.getSelectedRows(); + else + { + // if selection not allowed, consider ALL firsts to be selected + firstsSelected = new int[numFirsts]; + for (int i = 0; i < numFirsts; i++) + firstsSelected[i] = i; + } + int firstIndex = reverse ? firstsSelected.length - 1 : 0; + if (!reverse) + while (firstsSelected[firstIndex] <= firstLead) + firstIndex++; + else + while (firstsSelected[firstIndex] >= firstLead) + firstIndex--; + firstModel.addSelectionInterval(firstsSelected[firstIndex], + firstsSelected[firstIndex]); + secondModel.addSelectionInterval(secondLead, secondLead); + } + } + + /** + * A helper method for the key bindings. Used because the actions + * for TAB, SHIFT-TAB, ENTER, and SHIFT-ENTER are very similar. + * + * Selects the next (previous if SHIFT pressed) column (TAB) or row (ENTER) + * in the table, changing the current selection. All cells in the table + * are eligible, not just the ones that are currently selected. + * @param firstModel the ListSelectionModel for columns (TAB) or rows + * (ENTER) + * @param firstMax the last index in firstModel + * @param secondModel the ListSelectionModel for rows (TAB) or columns + * (ENTER) + * @param secondMax the last index in secondModel + * @param reverse true if SHIFT was pressed for the event + */ + + void advanceSingleSelection(ListSelectionModel firstModel, int firstMax, + ListSelectionModel secondModel, int secondMax, + boolean reverse) + { + // for TABs, "first" corresponds to columns and "seconds" to rows. + // the opposite is true for ENTERs + int firstLead = firstModel.getLeadSelectionIndex(); + int secondLead = secondModel.getLeadSelectionIndex(); + + // if we are going backwards subtract 2 because we later add 1 + // for a net change of -1 + if (reverse && (firstLead == 0)) + { + // check if we have to wrap around + if (secondLead == 0) + secondLead += secondMax + 1; + secondLead -= 2; + } + + // do we have to wrap the "seconds"? + if (reverse && (firstLead == 0) || !reverse && (firstLead == firstMax)) + secondModel.setSelectionInterval((secondLead + 1) % (secondMax + 1), + (secondLead + 1) % (secondMax + 1)); + // if not, just reselect the current lead + else + secondModel.setSelectionInterval(secondLead, secondLead); + + // if we are going backwards, subtract 2 because we add 1 later + // for net change of -1 + if (reverse) + { + // check for wraparound + if (firstLead == 0) + firstLead += firstMax + 1; + firstLead -= 2; + } + // select the next "first" + firstModel.setSelectionInterval((firstLead + 1) % (firstMax + 1), + (firstLead + 1) % (firstMax + 1)); + } + } + + protected void installListeners() + { + if (focusListener == null) + focusListener = createFocusListener(); + table.addFocusListener(focusListener); + if (keyListener == null) + keyListener = createKeyListener(); + table.addKeyListener(keyListener); + if (mouseInputListener == null) + mouseInputListener = createMouseInputListener(); + table.addMouseListener(mouseInputListener); + table.addMouseMotionListener(mouseInputListener); + if (propertyChangeListener == null) + propertyChangeListener = new PropertyChangeHandler(); + table.addPropertyChangeListener(propertyChangeListener); + } + + /** + * Uninstalls UI defaults that have been installed by + * {@link #installDefaults()}. + */ + protected void uninstallDefaults() + { + // Nothing to do here for now. + } + + /** + * Uninstalls the keyboard actions that have been installed by + * {@link #installKeyboardActions()}. + */ + protected void uninstallKeyboardActions() + { + SwingUtilities.replaceUIInputMap(table, JComponent. + WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, null); + SwingUtilities.replaceUIActionMap(table, null); + } + + protected void uninstallListeners() + { + table.removeFocusListener(focusListener); + table.removeKeyListener(keyListener); + table.removeMouseListener(mouseInputListener); + table.removeMouseMotionListener(mouseInputListener); + table.removePropertyChangeListener(propertyChangeListener); + propertyChangeListener = null; + } + + public void installUI(JComponent comp) + { + table = (JTable) comp; + rendererPane = new CellRendererPane(); + table.add(rendererPane); + + installDefaults(); + installKeyboardActions(); + installListeners(); + } + + public void uninstallUI(JComponent c) + { + uninstallListeners(); + uninstallKeyboardActions(); + uninstallDefaults(); + + table.remove(rendererPane); + rendererPane = null; + table = null; + } + + /** + * Paints a single cell in the table. + * + * @param g The graphics context to paint in + * @param row The row number to paint + * @param col The column number to paint + * @param bounds The bounds of the cell to paint, assuming a coordinate + * system beginning at <code>(0,0)</code> in the upper left corner of the + * table + * @param rend A cell renderer to paint with + */ + void paintCell(Graphics g, int row, int col, Rectangle bounds, + TableCellRenderer rend) + { + Component comp = table.prepareRenderer(rend, row, col); + rendererPane.paintComponent(g, comp, table, bounds); + } + + /** + * Paint the associated table. + */ + public void paint(Graphics gfx, JComponent ignored) + { + int ncols = table.getColumnCount(); + int nrows = table.getRowCount(); + if (nrows == 0 || ncols == 0) + return; + + Rectangle clip = gfx.getClipBounds(); + + // Determine the range of cells that are within the clip bounds. + Point p1 = new Point(clip.x, clip.y); + int c0 = table.columnAtPoint(p1); + if (c0 == -1) + c0 = 0; + int r0 = table.rowAtPoint(p1); + if (r0 == -1) + r0 = 0; + Point p2 = new Point(clip.x + clip.width, clip.y + clip.height); + int cn = table.columnAtPoint(p2); + if (cn == -1) + cn = table.getColumnCount() - 1; + int rn = table.rowAtPoint(p2); + if (rn == -1) + rn = table.getRowCount() - 1; + + int columnMargin = table.getColumnModel().getColumnMargin(); + int rowMargin = table.getRowMargin(); + + TableColumnModel cmodel = table.getColumnModel(); + int[] widths = new int[cn + 1]; + for (int i = c0; i <= cn; i++) + { + widths[i] = cmodel.getColumn(i).getWidth() - columnMargin; + } + + Rectangle bounds = table.getCellRect(r0, c0, false); + // The left boundary of the area being repainted. + int left = bounds.x; + + // The top boundary of the area being repainted. + int top = bounds.y; + + // The bottom boundary of the area being repainted. + int bottom; + + // paint the cell contents + Color grid = table.getGridColor(); + for (int r = r0; r <= rn; ++r) + { + for (int c = c0; c <= cn; ++c) + { + bounds.width = widths[c]; + paintCell(gfx, r, c, bounds, table.getCellRenderer(r, c)); + bounds.x += widths[c] + columnMargin; + } + bounds.x = left; + bounds.y += table.getRowHeight(r); + // Update row height for tables with custom heights. + bounds.height = table.getRowHeight(r + 1) - rowMargin; + } + + bottom = bounds.y - rowMargin; + + // paint vertical grid lines + if (grid != null && table.getShowVerticalLines()) + { + Color save = gfx.getColor(); + gfx.setColor(grid); + int x = left - columnMargin; + for (int c = c0; c <= cn; ++c) + { + // The vertical grid is draw right from the cells, so we + // add before drawing. + x += widths[c] + columnMargin; + gfx.drawLine(x, top, x, bottom); + } + gfx.setColor(save); + } + + // paint horizontal grid lines + if (grid != null && table.getShowHorizontalLines()) + { + Color save = gfx.getColor(); + gfx.setColor(grid); + int y = top - rowMargin; + for (int r = r0; r <= rn; ++r) + { + // The horizontal grid is draw below the cells, so we + // add before drawing. + y += table.getRowHeight(r); + gfx.drawLine(left, y, p2.x, y); + } + gfx.setColor(save); + } + } +} diff --git a/libjava/classpath/javax/swing/plaf/basic/BasicTextAreaUI.java b/libjava/classpath/javax/swing/plaf/basic/BasicTextAreaUI.java new file mode 100644 index 000000000..b2541b453 --- /dev/null +++ b/libjava/classpath/javax/swing/plaf/basic/BasicTextAreaUI.java @@ -0,0 +1,116 @@ +/* BasicTextAreaUI.java -- + Copyright (C) 2004, 2006, 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 java.beans.PropertyChangeEvent; + +import javax.swing.JComponent; +import javax.swing.JTextArea; +import javax.swing.UIDefaults; +import javax.swing.plaf.ComponentUI; +import javax.swing.text.Element; +import javax.swing.text.PlainView; +import javax.swing.text.View; +import javax.swing.text.WrappedPlainView; + +public class BasicTextAreaUI extends BasicTextUI +{ + public static ComponentUI createUI(JComponent comp) + { + return new BasicTextAreaUI(); + } + + public BasicTextAreaUI() + { + // Nothing to do here. + } + + /** + * Create the view. Returns a WrappedPlainView if the text area + * has lineWrap set to true, otherwise returns a PlainView. If + * lineWrap is true has to check whether the wrap style is word + * or character and return an appropriate WrappedPlainView. + * + * @param elem the element to create a View for + * @return an appropriate View for the element + */ + public View create(Element elem) + { + JTextArea comp = (JTextArea) getComponent(); + if (comp.getLineWrap()) + { + if (comp.getWrapStyleWord()) + return new WrappedPlainView(elem, true); + else + return new WrappedPlainView(elem, false); + } + else + return new PlainView(elem); + } + + /** + * Returns the prefix for entries in the {@link UIDefaults} table. + * + * @return "TextArea" + */ + protected String getPropertyPrefix() + { + return "TextArea"; + } + + /** + * Receives notification whenever one of the text component's bound + * properties changes. This changes the view to WrappedPlainView + * if setLineWrap(true) is called, and back to PlainView if + * setLineWrap(false) is called. + * + * @param ev the property change event + */ + protected void propertyChange(PropertyChangeEvent ev) + { + JTextArea comp = (JTextArea) getComponent(); + if (ev.getPropertyName() == "lineWrap" + || ev.getPropertyName() == "wrapStyleWord") + { + // Changes the View (without modifying the document or it's listeners). + setView(create(textComponent.getDocument().getDefaultRootElement())); + } + } +} diff --git a/libjava/classpath/javax/swing/plaf/basic/BasicTextFieldUI.java b/libjava/classpath/javax/swing/plaf/basic/BasicTextFieldUI.java new file mode 100644 index 000000000..5f6a92757 --- /dev/null +++ b/libjava/classpath/javax/swing/plaf/basic/BasicTextFieldUI.java @@ -0,0 +1,118 @@ +/* BasicTextFieldUI.java + Copyright (C) 2004, 2006, 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 java.awt.Color; +import java.beans.PropertyChangeEvent; + +import javax.swing.JComponent; +import javax.swing.UIDefaults; +import javax.swing.plaf.ColorUIResource; +import javax.swing.plaf.ComponentUI; +import javax.swing.text.Element; +import javax.swing.text.FieldView; +import javax.swing.text.View; + +public class BasicTextFieldUI extends BasicTextUI +{ + public BasicTextFieldUI() + { + super(); + } + + public View create(Element elem) + { + return new FieldView(elem); + } + + public static ComponentUI createUI(JComponent c) + { + return new BasicTextFieldUI(); + } + + /** + * Returns the prefix for entries in the {@link UIDefaults} table. + * + * @return "TextField" + */ + protected String getPropertyPrefix() + { + return "TextField"; + } + + public void installUI(JComponent c) + { + super.installUI(c); + } + + /** + * Receives notification whenever one of the text component's bound + * properties changes. Here we check for the editable and enabled + * properties and adjust the background color accordingly. + * + * <p>The colors are only changed if they are not a + * <code>ColorUIResource</code>.</p> + * + * @param event the property change event + */ + protected void propertyChange(PropertyChangeEvent event) + { + if (event.getPropertyName().equals("editable")) + { + // Changing the color only if the current background is an instance of + // ColorUIResource is the behavior of the RI. + if (textComponent.getBackground() instanceof ColorUIResource) + { + Color c = null; + Color old = textComponent.getBackground(); + String prefix = getPropertyPrefix(); + if (! textComponent.isEnabled()) + c = SharedUIDefaults.getColor(prefix + ".disabledBackground"); + if (c == null && ! textComponent.isEditable()) + c = SharedUIDefaults.getColor(prefix + ".inactiveBackground"); + if (c == null) + c = SharedUIDefaults.getColor(prefix + ".background"); + if (c != null && c != old) + { + textComponent.setBackground(c); + } + } + } + } +} diff --git a/libjava/classpath/javax/swing/plaf/basic/BasicTextPaneUI.java b/libjava/classpath/javax/swing/plaf/basic/BasicTextPaneUI.java new file mode 100644 index 000000000..507e0a169 --- /dev/null +++ b/libjava/classpath/javax/swing/plaf/basic/BasicTextPaneUI.java @@ -0,0 +1,92 @@ +/* BasicTextPaneUI.java -- + Copyright (C) 2004 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 java.awt.Color; + +import javax.swing.JComponent; +import javax.swing.JTextPane; +import javax.swing.plaf.ColorUIResource; +import javax.swing.UIDefaults; +import javax.swing.plaf.ComponentUI; +import javax.swing.text.Style; +import javax.swing.text.StyleConstants; +import javax.swing.text.StyleContext; + +public class BasicTextPaneUI extends BasicEditorPaneUI +{ + public BasicTextPaneUI() + { + // Do nothing here. + } + + public static ComponentUI createUI(JComponent comp) + { + return new BasicTextPaneUI(); + } + + /** + * Returns the prefix for entries in the {@link UIDefaults} table. + * + * @return "TextPane" + */ + protected String getPropertyPrefix() + { + return "TextPane"; + } + + /** + * Installs this UI on the specified <code>JTextPane</code>. This calls the + * super implementation and then adds a default style to the text pane. + * + * @param c the text pane to install the UI to + */ + public void installUI(JComponent c) + { + super.installUI(c); + JTextPane tp = (JTextPane) c; + Style defaultStyle = tp.getStyle(StyleContext.DEFAULT_STYLE); + defaultStyle.addAttribute(StyleConstants.Foreground, + new ColorUIResource(Color.BLACK)); + defaultStyle.addAttribute(StyleConstants.FontFamily, "Serif"); + defaultStyle.addAttribute(StyleConstants.Italic, Boolean.FALSE); + defaultStyle.addAttribute(StyleConstants.Bold, Boolean.FALSE); + defaultStyle.addAttribute(StyleConstants.FontSize, new Integer(12)); + } +} diff --git a/libjava/classpath/javax/swing/plaf/basic/BasicTextUI.java b/libjava/classpath/javax/swing/plaf/basic/BasicTextUI.java new file mode 100644 index 000000000..bd7cc48b9 --- /dev/null +++ b/libjava/classpath/javax/swing/plaf/basic/BasicTextUI.java @@ -0,0 +1,1538 @@ +/* BasicTextUI.java -- + Copyright (C) 2002, 2003, 2004, 2005, 2006 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.Container; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.HeadlessException; +import java.awt.Insets; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.Shape; +import java.awt.Toolkit; +import java.awt.datatransfer.Clipboard; +import java.awt.datatransfer.StringSelection; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; + +import javax.swing.Action; +import javax.swing.ActionMap; +import javax.swing.InputMap; +import javax.swing.JComponent; +import javax.swing.LookAndFeel; +import javax.swing.SwingConstants; +import javax.swing.SwingUtilities; +import javax.swing.TransferHandler; +import javax.swing.UIManager; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import javax.swing.plaf.ActionMapUIResource; +import javax.swing.plaf.InputMapUIResource; +import javax.swing.plaf.TextUI; +import javax.swing.plaf.UIResource; +import javax.swing.text.AbstractDocument; +import javax.swing.text.AttributeSet; +import javax.swing.text.BadLocationException; +import javax.swing.text.Caret; +import javax.swing.text.DefaultCaret; +import javax.swing.text.DefaultEditorKit; +import javax.swing.text.DefaultHighlighter; +import javax.swing.text.Document; +import javax.swing.text.EditorKit; +import javax.swing.text.Element; +import javax.swing.text.Highlighter; +import javax.swing.text.JTextComponent; +import javax.swing.text.Keymap; +import javax.swing.text.Position; +import javax.swing.text.View; +import javax.swing.text.ViewFactory; + +/** + * The abstract base class from which the UI classes for Swings text + * components are derived. This provides most of the functionality for + * the UI classes. + * + * @author original author unknown + * @author Roman Kennke (roman@kennke.org) + */ +public abstract class BasicTextUI extends TextUI + implements ViewFactory +{ + /** + * A {@link DefaultCaret} that implements {@link UIResource}. + */ + public static class BasicCaret extends DefaultCaret implements UIResource + { + public BasicCaret() + { + // Nothing to do here. + } + } + + /** + * A {@link DefaultHighlighter} that implements {@link UIResource}. + */ + public static class BasicHighlighter extends DefaultHighlighter + implements UIResource + { + public BasicHighlighter() + { + // Nothing to do here. + } + } + + private static class FocusHandler + implements FocusListener + { + public void focusGained(FocusEvent e) + { + // Nothing to do here. + } + public void focusLost(FocusEvent e) + { + JTextComponent textComponent = (JTextComponent) e.getComponent(); + // Integrates Swing text components with the system clipboard: + // The idea is that if one wants to copy text around X11-style + // (select text and middle-click in the target component) the focus + // will move to the new component which gives the old focus owner the + // possibility to paste its selection into the clipboard. + if (!e.isTemporary() + && textComponent.getSelectionStart() + != textComponent.getSelectionEnd()) + { + SecurityManager sm = System.getSecurityManager(); + try + { + if (sm != null) + sm.checkSystemClipboardAccess(); + + Clipboard cb = Toolkit.getDefaultToolkit().getSystemSelection(); + if (cb != null) + { + StringSelection selection = new StringSelection( + textComponent.getSelectedText()); + cb.setContents(selection, selection); + } + } + catch (SecurityException se) + { + // Not allowed to access the clipboard: Ignore and + // do not access it. + } + catch (HeadlessException he) + { + // There is no AWT: Ignore and do not access the + // clipboard. + } + catch (IllegalStateException ise) + { + // Clipboard is currently unavaible. + } + } + } + } + + /** + * This FocusListener triggers repaints on focus shift. + */ + private static FocusListener focusListener; + + /** + * Receives notifications when properties of the text component change. + */ + private class Handler + implements PropertyChangeListener, DocumentListener + { + /** + * Notifies when a property of the text component changes. + * + * @param event the PropertyChangeEvent describing the change + */ + public void propertyChange(PropertyChangeEvent event) + { + if (event.getPropertyName().equals("document")) + { + // Document changed. + Object oldValue = event.getOldValue(); + if (oldValue != null) + { + Document oldDoc = (Document) oldValue; + oldDoc.removeDocumentListener(handler); + } + Object newValue = event.getNewValue(); + if (newValue != null) + { + Document newDoc = (Document) newValue; + newDoc.addDocumentListener(handler); + } + modelChanged(); + } + + BasicTextUI.this.propertyChange(event); + } + + /** + * Notification about a document change event. + * + * @param ev the DocumentEvent describing the change + */ + public void changedUpdate(DocumentEvent ev) + { + // Updates are forwarded to the View even if 'getVisibleEditorRect' + // method returns null. This means the View classes have to be + // aware of that possibility. + rootView.changedUpdate(ev, getVisibleEditorRect(), + rootView.getViewFactory()); + } + + /** + * Notification about a document insert event. + * + * @param ev the DocumentEvent describing the insertion + */ + public void insertUpdate(DocumentEvent ev) + { + // Updates are forwarded to the View even if 'getVisibleEditorRect' + // method returns null. This means the View classes have to be + // aware of that possibility. + rootView.insertUpdate(ev, getVisibleEditorRect(), + rootView.getViewFactory()); + } + + /** + * Notification about a document removal event. + * + * @param ev the DocumentEvent describing the removal + */ + public void removeUpdate(DocumentEvent ev) + { + // Updates are forwarded to the View even if 'getVisibleEditorRect' + // method returns null. This means the View classes have to be + // aware of that possibility. + rootView.removeUpdate(ev, getVisibleEditorRect(), + rootView.getViewFactory()); + } + + } + + /** + * This view forms the root of the View hierarchy. However, it delegates + * most calls to another View which is the real root of the hierarchy. + * The purpose is to make sure that all Views in the hierarchy, including + * the (real) root have a well-defined parent to which they can delegate + * calls like {@link #preferenceChanged}, {@link #getViewFactory} and + * {@link #getContainer}. + */ + private class RootView extends View + { + /** The real root view. */ + private View view; + + /** + * Creates a new RootView. + */ + public RootView() + { + super(null); + } + + /** + * Returns the ViewFactory for this RootView. If the current EditorKit + * provides a ViewFactory, this is used. Otherwise the TextUI itself + * is returned as a ViewFactory. + * + * @return the ViewFactory for this RootView + */ + public ViewFactory getViewFactory() + { + ViewFactory factory = null; + EditorKit editorKit = BasicTextUI.this.getEditorKit(getComponent()); + factory = editorKit.getViewFactory(); + if (factory == null) + factory = BasicTextUI.this; + return factory; + } + + /** + * Indicates that the preferences of one of the child view has changed. + * This calls revalidate on the text component. + * + * @param v the child view which's preference has changed + * @param width <code>true</code> if the width preference has changed + * @param height <code>true</code> if the height preference has changed + */ + public void preferenceChanged(View v, boolean width, boolean height) + { + textComponent.revalidate(); + } + + /** + * Sets the real root view. + * + * @param v the root view to set + */ + public void setView(View v) + { + if (view != null) + view.setParent(null); + + if (v != null) + v.setParent(this); + + view = v; + } + + /** + * Returns the real root view, regardless of the index. + * + * @param index not used here + * + * @return the real root view, regardless of the index. + */ + public View getView(int index) + { + return view; + } + + /** + * Returns <code>1</code> since the RootView always contains one + * child, that is the real root of the View hierarchy. + * + * @return <code>1</code> since the RootView always contains one + * child, that is the real root of the View hierarchy + */ + public int getViewCount() + { + int count = 0; + if (view != null) + count = 1; + return count; + } + + /** + * Returns the <code>Container</code> that contains this view. This + * normally will be the text component that is managed by this TextUI. + * + * @return the <code>Container</code> that contains this view + */ + public Container getContainer() + { + return textComponent; + } + + /** + * Sets the size of the renderer. This is synchronized because that + * potentially triggers layout and we don't want more than one thread + * playing with the layout information. + */ + public synchronized void setSize(float w, float h) + { + if (view != null) + view.setSize(w, h); + } + + /** + * Paints the view. This is delegated to the real root view. + * + * @param g the <code>Graphics</code> context to paint to + * @param s the allocation for the View + */ + public void paint(Graphics g, Shape s) + { + if (view != null) + { + Rectangle b = s instanceof Rectangle ? (Rectangle) s : s.getBounds(); + setSize(b.width, b.height); + view.paint(g, s); + } + } + + + /** + * Maps a position in the document into the coordinate space of the View. + * The output rectangle usually reflects the font height but has a width + * of zero. + * + * This is delegated to the real root view. + * + * @param position the position of the character in the model + * @param a the area that is occupied by the view + * @param bias either {@link Position.Bias#Forward} or + * {@link Position.Bias#Backward} depending on the preferred + * direction bias. If <code>null</code> this defaults to + * <code>Position.Bias.Forward</code> + * + * @return a rectangle that gives the location of the document position + * inside the view coordinate space + * + * @throws BadLocationException if <code>pos</code> is invalid + * @throws IllegalArgumentException if b is not one of the above listed + * valid values + */ + public Shape modelToView(int position, Shape a, Position.Bias bias) + throws BadLocationException + { + return view.modelToView(position, a, bias); + } + + /** + * Maps coordinates from the <code>View</code>'s space into a position + * in the document model. + * + * @param x the x coordinate in the view space + * @param y the y coordinate in the view space + * @param a the allocation of this <code>View</code> + * @param b the bias to use + * + * @return the position in the document that corresponds to the screen + * coordinates <code>x, y</code> + */ + public int viewToModel(float x, float y, Shape a, Position.Bias[] b) + { + return view.viewToModel(x, y, a, b); + } + + /** + * Notification about text insertions. These are forwarded to the + * real root view. + * + * @param ev the DocumentEvent describing the change + * @param shape the current allocation of the view's display + * @param vf the ViewFactory to use for creating new Views + */ + public void insertUpdate(DocumentEvent ev, Shape shape, ViewFactory vf) + { + if (view != null) + view.insertUpdate(ev, shape, vf); + } + + /** + * Notification about text removals. These are forwarded to the + * real root view. + * + * @param ev the DocumentEvent describing the change + * @param shape the current allocation of the view's display + * @param vf the ViewFactory to use for creating new Views + */ + public void removeUpdate(DocumentEvent ev, Shape shape, ViewFactory vf) + { + if (view != null) + view.removeUpdate(ev, shape, vf); + } + + /** + * Notification about text changes. These are forwarded to the + * real root view. + * + * @param ev the DocumentEvent describing the change + * @param shape the current allocation of the view's display + * @param vf the ViewFactory to use for creating new Views + */ + public void changedUpdate(DocumentEvent ev, Shape shape, ViewFactory vf) + { + if (view != null) + view.changedUpdate(ev, shape, vf); + } + + /** + * Returns the document position that is (visually) nearest to the given + * document position <code>pos</code> in the given direction <code>d</code>. + * + * @param pos the document position + * @param b the bias for <code>pos</code> + * @param a the allocation for the view + * @param d the direction, must be either {@link SwingConstants#NORTH}, + * {@link SwingConstants#SOUTH}, {@link SwingConstants#WEST} or + * {@link SwingConstants#EAST} + * @param biasRet an array of {@link Position.Bias} that can hold at least + * one element, which is filled with the bias of the return position + * on method exit + * + * @return the document position that is (visually) nearest to the given + * document position <code>pos</code> in the given direction + * <code>d</code> + * + * @throws BadLocationException if <code>pos</code> is not a valid offset in + * the document model + */ + public int getNextVisualPositionFrom(int pos, Position.Bias b, Shape a, + int d, Position.Bias[] biasRet) + throws BadLocationException + { + return view.getNextVisualPositionFrom(pos, b, a, d, biasRet); + } + + /** + * Returns the startOffset of this view, which is always the beginning + * of the document. + * + * @return the startOffset of this view + */ + public int getStartOffset() + { + return 0; + } + + /** + * Returns the endOffset of this view, which is always the end + * of the document. + * + * @return the endOffset of this view + */ + public int getEndOffset() + { + return getDocument().getLength(); + } + + /** + * Returns the document associated with this view. + * + * @return the document associated with this view + */ + public Document getDocument() + { + return textComponent.getDocument(); + } + + /** + * Returns the attributes, which is null for the RootView. + */ + public AttributeSet getAttributes() + { + return null; + } + + /** + * Overridden to forward to the view. + */ + public float getPreferredSpan(int axis) + { + // The RI returns 10 in the degenerate case. + float span = 10; + if (view != null) + span = view.getPreferredSpan(axis); + return span; + } + + /** + * Overridden to forward to the real view. + */ + public float getMinimumSpan(int axis) + { + // The RI returns 10 in the degenerate case. + float span = 10; + if (view != null) + span = view.getMinimumSpan(axis); + return span; + } + + /** + * Overridden to return Integer.MAX_VALUE. + */ + public float getMaximumSpan(int axis) + { + // The RI returns Integer.MAX_VALUE here, regardless of the real view's + // maximum size. + return Integer.MAX_VALUE; + } + } + + /** + * The EditorKit used by this TextUI. + */ + private static EditorKit kit; + + /** + * The combined event handler for text components. + * + * This is package private to avoid accessor methods. + */ + Handler handler; + + /** + * The root view. + * + * This is package private to avoid accessor methods. + */ + RootView rootView; + + /** + * The text component that we handle. + */ + JTextComponent textComponent; + + /** + * Creates a new <code>BasicTextUI</code> instance. + */ + public BasicTextUI() + { + // Nothing to do here. + } + + /** + * Creates a {@link Caret} that should be installed into the text component. + * + * @return a caret that should be installed into the text component + */ + protected Caret createCaret() + { + return new BasicCaret(); + } + + /** + * Creates a {@link Highlighter} that should be installed into the text + * component. + * + * @return a <code>Highlighter</code> for the text component + */ + protected Highlighter createHighlighter() + { + return new BasicHighlighter(); + } + + /** + * The text component that is managed by this UI. + * + * @return the text component that is managed by this UI + */ + protected final JTextComponent getComponent() + { + return textComponent; + } + + /** + * Installs this UI on the text component. + * + * @param c the text component on which to install the UI + */ + public void installUI(final JComponent c) + { + textComponent = (JTextComponent) c; + + if (rootView == null) + rootView = new RootView(); + + installDefaults(); + installFixedDefaults(); + + // These listeners must be installed outside of installListeners(), + // because overriding installListeners() doesn't prevent installing + // these in the RI, but overriding isntallUI() does. + if (handler == null) + handler = new Handler(); + textComponent.addPropertyChangeListener(handler); + Document doc = textComponent.getDocument(); + if (doc == null) + { + // The Handler takes care of installing the necessary listeners + // on the document here. + doc = getEditorKit(textComponent).createDefaultDocument(); + textComponent.setDocument(doc); + } + else + { + // Must install the document listener. + doc.addDocumentListener(handler); + modelChanged(); + } + + installListeners(); + installKeyboardActions(); + } + + /** + * Installs UI defaults on the text components. + */ + protected void installDefaults() + { + String prefix = getPropertyPrefix(); + // Install the standard properties. + LookAndFeel.installColorsAndFont(textComponent, prefix + ".background", + prefix + ".foreground", prefix + ".font"); + LookAndFeel.installBorder(textComponent, prefix + ".border"); + + // Some additional text component only properties. + Color color = textComponent.getCaretColor(); + if (color == null || color instanceof UIResource) + { + color = UIManager.getColor(prefix + ".caretForeground"); + textComponent.setCaretColor(color); + } + + // Fetch the colors for enabled/disabled text components. + color = textComponent.getDisabledTextColor(); + if (color == null || color instanceof UIResource) + { + color = UIManager.getColor(prefix + ".inactiveForeground"); + textComponent.setDisabledTextColor(color); + } + color = textComponent.getSelectedTextColor(); + if (color == null || color instanceof UIResource) + { + color = UIManager.getColor(prefix + ".selectionForeground"); + textComponent.setSelectedTextColor(color); + } + color = textComponent.getSelectionColor(); + if (color == null || color instanceof UIResource) + { + color = UIManager.getColor(prefix + ".selectionBackground"); + textComponent.setSelectionColor(color); + } + + Insets margin = textComponent.getMargin(); + if (margin == null || margin instanceof UIResource) + { + margin = UIManager.getInsets(prefix + ".margin"); + textComponent.setMargin(margin); + } + + } + + /** + * Installs defaults that can't be overridden by overriding + * installDefaults(). + */ + private void installFixedDefaults() + { + String prefix = getPropertyPrefix(); + Caret caret = textComponent.getCaret(); + if (caret == null || caret instanceof UIResource) + { + caret = createCaret(); + textComponent.setCaret(caret); + caret.setBlinkRate(UIManager.getInt(prefix + ".caretBlinkRate")); + } + + Highlighter highlighter = textComponent.getHighlighter(); + if (highlighter == null || highlighter instanceof UIResource) + textComponent.setHighlighter(createHighlighter()); + + } + + /** + * Install all listeners on the text component. + */ + protected void installListeners() + { + // + if (SystemProperties.getProperty("gnu.swing.text.no-xlike-clipboard") + == null) + { + if (focusListener == null) + focusListener = new FocusHandler(); + textComponent.addFocusListener(focusListener); + } + } + + /** + * Returns the name of the keymap for this type of TextUI. + * + * This is implemented so that the classname of this TextUI + * without the package prefix is returned. This way subclasses + * don't have to override this method. + * + * @return the name of the keymap for this TextUI + */ + protected String getKeymapName() + { + String fullClassName = getClass().getName(); + int index = fullClassName.lastIndexOf('.'); + String className = fullClassName.substring(index + 1); + return className; + } + + /** + * Creates the {@link Keymap} that is installed on the text component. + * + * @return the {@link Keymap} that is installed on the text component + */ + protected Keymap createKeymap() + { + String keymapName = getKeymapName(); + Keymap keymap = JTextComponent.getKeymap(keymapName); + if (keymap == null) + { + Keymap parentMap = + JTextComponent.getKeymap(JTextComponent.DEFAULT_KEYMAP); + keymap = JTextComponent.addKeymap(keymapName, parentMap); + Object val = UIManager.get(getPropertyPrefix() + ".keyBindings"); + if (val != null && val instanceof JTextComponent.KeyBinding[]) + { + JTextComponent.KeyBinding[] bindings = + (JTextComponent.KeyBinding[]) val; + JTextComponent.loadKeymap(keymap, bindings, + getComponent().getActions()); + } + } + return keymap; + } + + /** + * Installs the keyboard actions on the text components. + */ + protected void installKeyboardActions() + { + // This is only there for backwards compatibility. + textComponent.setKeymap(createKeymap()); + + // load any bindings for the newer InputMap / ActionMap interface + SwingUtilities.replaceUIInputMap(textComponent, JComponent.WHEN_FOCUSED, + getInputMap()); + SwingUtilities.replaceUIActionMap(textComponent, getActionMap()); + } + + /** + * Creates an ActionMap to be installed on the text component. + * + * @return an ActionMap to be installed on the text component + */ + private ActionMap getActionMap() + { + // Note: There are no .actionMap entries in the standard L&Fs. However, + // with the RI it is possible to install action maps via such keys, so + // we must load them too. It can be observed that when there is no + // .actionMap entry in the UIManager, one gets installed after a text + // component of that type has been loaded. + String prefix = getPropertyPrefix(); + String amName = prefix + ".actionMap"; + ActionMap am = (ActionMap) UIManager.get(amName); + if (am == null) + { + am = createActionMap(); + UIManager.put(amName, am); + } + + ActionMap map = new ActionMapUIResource(); + map.setParent(am); + + return map; + } + + /** + * Creates a default ActionMap for text components that have no UI default + * for this (the standard for the built-in L&Fs). The ActionMap is copied + * from the text component's getActions() method. + * + * @returna default ActionMap + */ + private ActionMap createActionMap() + { + ActionMap am = new ActionMapUIResource(); + Action[] actions = textComponent.getActions(); + for (int i = actions.length - 1; i >= 0; i--) + { + Action action = actions[i]; + am.put(action.getValue(Action.NAME), action); + } + // Add TransferHandler's actions here. They don't seem to be in the + // JTextComponent's default actions, and I can't make up a better place + // to add them. + Action copyAction = TransferHandler.getCopyAction(); + am.put(copyAction.getValue(Action.NAME), copyAction); + Action cutAction = TransferHandler.getCutAction(); + am.put(cutAction.getValue(Action.NAME), cutAction); + Action pasteAction = TransferHandler.getPasteAction(); + am.put(pasteAction.getValue(Action.NAME), pasteAction); + + return am; + } + + /** + * Gets the input map for the specified <code>condition</code>. + * + * @return the InputMap for the specified condition + */ + private InputMap getInputMap() + { + InputMap im = new InputMapUIResource(); + String prefix = getPropertyPrefix(); + InputMap shared = + (InputMap) SharedUIDefaults.get(prefix + ".focusInputMap"); + if (shared != null) + im.setParent(shared); + return im; + } + + /** + * Uninstalls this TextUI from the text component. + * + * @param component the text component to uninstall the UI from + */ + public void uninstallUI(final JComponent component) + { + textComponent.removePropertyChangeListener(handler); + textComponent.getDocument().removeDocumentListener(handler); + rootView.setView(null); + + uninstallDefaults(); + uninstallFixedDefaults(); + uninstallListeners(); + uninstallKeyboardActions(); + + textComponent = null; + } + + /** + * Uninstalls all default properties that have previously been installed by + * this UI. + */ + protected void uninstallDefaults() + { + if (textComponent.getCaretColor() instanceof UIResource) + textComponent.setCaretColor(null); + if (textComponent.getSelectionColor() instanceof UIResource) + textComponent.setSelectionColor(null); + if (textComponent.getDisabledTextColor() instanceof UIResource) + textComponent.setDisabledTextColor(null); + if (textComponent.getSelectedTextColor() instanceof UIResource) + textComponent.setSelectedTextColor(null); + LookAndFeel.uninstallBorder(textComponent); + if (textComponent.getMargin() instanceof UIResource) + textComponent.setMargin(null); + } + + /** + * Uninstalls additional fixed defaults that were installed + * by installFixedDefaults(). + */ + private void uninstallFixedDefaults() + { + if (textComponent.getCaret() instanceof UIResource) + textComponent.setCaret(null); + if (textComponent.getHighlighter() instanceof UIResource) + textComponent.setHighlighter(null); + } + + /** + * Uninstalls all listeners that have previously been installed by + * this UI. + */ + protected void uninstallListeners() + { + // Don't nullify the focusListener field, as it is static and shared + // between components. + if (focusListener != null) + textComponent.removeFocusListener(focusListener); + } + + /** + * Uninstalls all keyboard actions that have previously been installed by + * this UI. + */ + protected void uninstallKeyboardActions() + { + SwingUtilities.replaceUIInputMap(textComponent, JComponent.WHEN_FOCUSED, + null); + SwingUtilities.replaceUIActionMap(textComponent, null); + } + + /** + * Returns the property prefix by which the text component's UIDefaults + * are looked up. + * + * @return the property prefix by which the text component's UIDefaults + * are looked up + */ + protected abstract String getPropertyPrefix(); + + /** + * Returns the preferred size of the text component. + * + * @param c not used here + * + * @return the preferred size of the text component + */ + public Dimension getPreferredSize(JComponent c) + { + Dimension d = c.getSize(); + Insets i = c.getInsets(); + // We need to lock here, since we require the view hierarchy to _not_ + // change in between. + float w; + float h; + Document doc = textComponent.getDocument(); + if (doc instanceof AbstractDocument) + ((AbstractDocument) doc).readLock(); + try + { + if (d.width > (i.left + i.right) && d.height > (i.top + i.bottom)) + { + rootView.setSize(d.width - i.left - i.right, + d.height - i.top - i.bottom); + } + else + { + // Not laid out yet. Force some pseudo size. + rootView.setSize(Integer.MAX_VALUE, Integer.MAX_VALUE); + } + w = rootView.getPreferredSpan(View.X_AXIS); + h = rootView.getPreferredSpan(View.Y_AXIS); + } + finally + { + if (doc instanceof AbstractDocument) + ((AbstractDocument) doc).readUnlock(); + } + Dimension size = new Dimension((int) w + i.left + i.right, + (int) h + i.top + i.bottom); + return size; + } + + /** + * Returns the maximum size for text components that use this UI. + * + * This returns (Integer.MAX_VALUE, Integer.MAX_VALUE). + * + * @param c not used here + * + * @return the maximum size for text components that use this UI + */ + public Dimension getMaximumSize(JComponent c) + { + Dimension d = new Dimension(); + Insets i = c.getInsets(); + Document doc = textComponent.getDocument(); + // We need to lock here, since we require the view hierarchy to _not_ + // change in between. + if (doc instanceof AbstractDocument) + ((AbstractDocument) doc).readLock(); + try + { + // Check for overflow here. + d.width = (int) Math.min((long) rootView.getMaximumSpan(View.X_AXIS) + + i.left + i.right, Integer.MAX_VALUE); + d.height = (int) Math.min((long) rootView.getMaximumSpan(View.Y_AXIS) + + i.top + i.bottom, Integer.MAX_VALUE); + } + finally + { + if (doc instanceof AbstractDocument) + ((AbstractDocument) doc).readUnlock(); + } + return d; + } + + /** + * Returns the minimum size for text components. This returns the size + * of the component's insets. + * + * @return the minimum size for text components + */ + public Dimension getMinimumSize(JComponent c) + { + Dimension d = new Dimension(); + Document doc = textComponent.getDocument(); + // We need to lock here, since we require the view hierarchy to _not_ + // change in between. + if (doc instanceof AbstractDocument) + ((AbstractDocument) doc).readLock(); + try + { + d.width = (int) rootView.getMinimumSpan(View.X_AXIS); + d.height = (int) rootView.getMinimumSpan(View.Y_AXIS); + } + finally + { + if (doc instanceof AbstractDocument) + ((AbstractDocument) doc).readUnlock(); + } + Insets i = c.getInsets(); + d.width += i.left + i.right; + d.height += i.top + i.bottom; + return d; + } + + /** + * Paints the text component. This acquires a read lock on the model and then + * calls {@link #paintSafely(Graphics)} in order to actually perform the + * painting. + * + * @param g the <code>Graphics</code> context to paint to + * @param c not used here + */ + public final void paint(Graphics g, JComponent c) + { + try + { + Document doc = textComponent.getDocument(); + if (doc instanceof AbstractDocument) + { + AbstractDocument aDoc = (AbstractDocument) doc; + aDoc.readLock(); + } + paintSafely(g); + } + finally + { + Document doc = textComponent.getDocument(); + if (doc instanceof AbstractDocument) + { + AbstractDocument aDoc = (AbstractDocument) doc; + aDoc.readUnlock(); + } + } + } + + /** + * This paints the text component while beeing sure that the model is not + * modified while painting. + * + * The following is performed in this order: + * <ol> + * <li>If the text component is opaque, the background is painted by + * calling {@link #paintBackground(Graphics)}.</li> + * <li>If there is a highlighter, the highlighter is painted.</li> + * <li>The view hierarchy is painted.</li> + * <li>The Caret is painter.</li> + * </ol> + * + * @param g the <code>Graphics</code> context to paint to + */ + protected void paintSafely(Graphics g) + { + Caret caret = textComponent.getCaret(); + Highlighter highlighter = textComponent.getHighlighter(); + + if (textComponent.isOpaque()) + paintBackground(g); + + // Try painting with the highlighter without checking whether there + // is a selection because a highlighter can be used to do more than + // marking selected text. + if (highlighter != null) + { + // Handle restoring of the color here to prevent + // drawing problems when the Highlighter implementor + // forgets to restore it. + Color oldColor = g.getColor(); + highlighter.paint(g); + g.setColor(oldColor); + } + + rootView.paint(g, getVisibleEditorRect()); + + if (caret != null && textComponent.hasFocus()) + caret.paint(g); + } + + /** + * Paints the background of the text component. + * + * @param g the <code>Graphics</code> context to paint to + */ + protected void paintBackground(Graphics g) + { + Color old = g.getColor(); + g.setColor(textComponent.getBackground()); + g.fillRect(0, 0, textComponent.getWidth(), textComponent.getHeight()); + g.setColor(old); + } + + /** + * Overridden for better control over background painting. This now simply + * calls {@link #paint} and this delegates the background painting to + * {@link #paintBackground}. + * + * @param g the graphics to use + * @param c the component to be painted + */ + public void update(Graphics g, JComponent c) + { + paint(g, c); + } + + /** + * Marks the specified range inside the text component's model as + * damaged and queues a repaint request. + * + * @param t the text component + * @param p0 the start location inside the document model of the range that + * is damaged + * @param p1 the end location inside the document model of the range that + * is damaged + */ + public void damageRange(JTextComponent t, int p0, int p1) + { + damageRange(t, p0, p1, Position.Bias.Forward, Position.Bias.Backward); + } + + /** + * Marks the specified range inside the text component's model as + * damaged and queues a repaint request. This variant of this method + * allows a {@link Position.Bias} object to be specified for the start + * and end location of the range. + * + * @param t the text component + * @param p0 the start location inside the document model of the range that + * is damaged + * @param p1 the end location inside the document model of the range that + * is damaged + * @param firstBias the bias for the start location + * @param secondBias the bias for the end location + */ + public void damageRange(JTextComponent t, int p0, int p1, + Position.Bias firstBias, Position.Bias secondBias) + { + Rectangle alloc = getVisibleEditorRect(); + if (alloc != null) + { + Document doc = t.getDocument(); + + // Acquire lock here to avoid structural changes in between. + if (doc instanceof AbstractDocument) + ((AbstractDocument) doc).readLock(); + try + { + rootView.setSize(alloc.width, alloc.height); + Shape damage = rootView.modelToView(p0, firstBias, p1, secondBias, + alloc); + Rectangle r = damage instanceof Rectangle ? (Rectangle) damage + : damage.getBounds(); + textComponent.repaint(r.x, r.y, r.width, r.height); + } + catch (BadLocationException ex) + { + // Lets ignore this as it causes no serious problems. + // For debugging, comment this out. + // ex.printStackTrace(); + } + finally + { + // Release lock. + if (doc instanceof AbstractDocument) + ((AbstractDocument) doc).readUnlock(); + } + } + } + + /** + * Returns the {@link EditorKit} used for the text component that is managed + * by this UI. + * + * @param t the text component + * + * @return the {@link EditorKit} used for the text component that is managed + * by this UI + */ + public EditorKit getEditorKit(JTextComponent t) + { + if (kit == null) + kit = new DefaultEditorKit(); + return kit; + } + + /** + * Gets the next position inside the document model that is visible on + * screen, starting from <code>pos</code>. + * + * @param t the text component + * @param pos the start positionn + * @param b the bias for pos + * @param direction the search direction + * @param biasRet filled by the method to indicate the bias of the return + * value + * + * @return the next position inside the document model that is visible on + * screen + */ + public int getNextVisualPositionFrom(JTextComponent t, int pos, + Position.Bias b, int direction, + Position.Bias[] biasRet) + throws BadLocationException + { + int offset = -1; + Document doc = textComponent.getDocument(); + if (doc instanceof AbstractDocument) + ((AbstractDocument) doc).readLock(); + try + { + Rectangle alloc = getVisibleEditorRect(); + if (alloc != null) + { + rootView.setSize(alloc.width, alloc.height); + offset = rootView.getNextVisualPositionFrom(pos, b, alloc, + direction, biasRet); + } + } + finally + { + if (doc instanceof AbstractDocument) + ((AbstractDocument) doc).readUnlock(); + } + return offset; + } + + /** + * Returns the root {@link View} of a text component. + * + * @return the root {@link View} of a text component + */ + public View getRootView(JTextComponent t) + { + return rootView; + } + + /** + * Maps a position in the document into the coordinate space of the View. + * The output rectangle usually reflects the font height but has a width + * of zero. A bias of {@link Position.Bias#Forward} is used in this method. + * + * @param t the text component + * @param pos the position of the character in the model + * + * @return a rectangle that gives the location of the document position + * inside the view coordinate space + * + * @throws BadLocationException if <code>pos</code> is invalid + * @throws IllegalArgumentException if b is not one of the above listed + * valid values + */ + public Rectangle modelToView(JTextComponent t, int pos) + throws BadLocationException + { + return modelToView(t, pos, Position.Bias.Forward); + } + + /** + * Maps a position in the document into the coordinate space of the View. + * The output rectangle usually reflects the font height but has a width + * of zero. + * + * @param t the text component + * @param pos the position of the character in the model + * @param bias either {@link Position.Bias#Forward} or + * {@link Position.Bias#Backward} depending on the preferred + * direction bias. If <code>null</code> this defaults to + * <code>Position.Bias.Forward</code> + * + * @return a rectangle that gives the location of the document position + * inside the view coordinate space + * + * @throws BadLocationException if <code>pos</code> is invalid + * @throws IllegalArgumentException if b is not one of the above listed + * valid values + */ + public Rectangle modelToView(JTextComponent t, int pos, Position.Bias bias) + throws BadLocationException + { + // We need to read-lock here because we depend on the document + // structure not beeing changed in between. + Document doc = textComponent.getDocument(); + if (doc instanceof AbstractDocument) + ((AbstractDocument) doc).readLock(); + Rectangle rect = null; + try + { + Rectangle r = getVisibleEditorRect(); + if (r != null) + { + rootView.setSize(r.width, r.height); + Shape s = rootView.modelToView(pos, r, bias); + if (s != null) + rect = s.getBounds(); + } + } + finally + { + if (doc instanceof AbstractDocument) + ((AbstractDocument) doc).readUnlock(); + } + return rect; + } + + /** + * Maps a point in the <code>View</code> coordinate space to a position + * inside a document model. + * + * @param t the text component + * @param pt the point to be mapped + * + * @return the position inside the document model that corresponds to + * <code>pt</code> + */ + public int viewToModel(JTextComponent t, Point pt) + { + return viewToModel(t, pt, new Position.Bias[1]); + } + + /** + * Maps a point in the <code>View</code> coordinate space to a position + * inside a document model. + * + * @param t the text component + * @param pt the point to be mapped + * @param biasReturn filled in by the method to indicate the bias of the + * return value + * + * @return the position inside the document model that corresponds to + * <code>pt</code> + */ + public int viewToModel(JTextComponent t, Point pt, Position.Bias[] biasReturn) + { + int offset = -1; + Document doc = textComponent.getDocument(); + if (doc instanceof AbstractDocument) + ((AbstractDocument) doc).readLock(); + try + { + Rectangle alloc = getVisibleEditorRect(); + if (alloc != null) + { + rootView.setSize(alloc.width, alloc.height); + offset = rootView.viewToModel(pt.x, pt.y, alloc, biasReturn); + } + } + finally + { + if (doc instanceof AbstractDocument) + ((AbstractDocument) doc).readUnlock(); + } + return offset; + } + + /** + * Creates a {@link View} for the specified {@link Element}. + * + * @param elem the <code>Element</code> to create a <code>View</code> for + * + * @see ViewFactory + */ + public View create(Element elem) + { + // Subclasses have to implement this to get this functionality. + return null; + } + + /** + * Creates a {@link View} for the specified {@link Element}. + * + * @param elem the <code>Element</code> to create a <code>View</code> for + * @param p0 the start offset + * @param p1 the end offset + * + * @see ViewFactory + */ + public View create(Element elem, int p0, int p1) + { + // Subclasses have to implement this to get this functionality. + return null; + } + + /** + * A cached Insets instance to be reused below. + */ + private Insets cachedInsets; + + /** + * Returns the allocation to give the root view. + * + * @return the allocation to give the root view + * + * @specnote The allocation has nothing to do with visibility. According + * to the specs the naming of this method is unfortunate and + * has historical reasons + */ + protected Rectangle getVisibleEditorRect() + { + int width = textComponent.getWidth(); + int height = textComponent.getHeight(); + + // Return null if the component has no valid size. + if (width <= 0 || height <= 0) + return null; + + Insets insets = textComponent.getInsets(cachedInsets); + return new Rectangle(insets.left, insets.top, + width - insets.left - insets.right, + height - insets.top - insets.bottom); + } + + /** + * Sets the root view for the text component. + * + * @param view the <code>View</code> to be set as root view + */ + protected final void setView(View view) + { + rootView.setView(view); + textComponent.revalidate(); + textComponent.repaint(); + } + + /** + * Indicates that the model of a text component has changed. This + * triggers a rebuild of the view hierarchy. + */ + protected void modelChanged() + { + if (textComponent == null || rootView == null) + return; + ViewFactory factory = rootView.getViewFactory(); + if (factory == null) + return; + Document doc = textComponent.getDocument(); + if (doc == null) + return; + Element elem = doc.getDefaultRootElement(); + if (elem == null) + return; + View view = factory.create(elem); + setView(view); + } + + /** + * Receives notification whenever one of the text component's bound + * properties changes. This default implementation does nothing. + * It is a hook that enables subclasses to react to property changes + * on the text component. + * + * @param ev the property change event + */ + protected void propertyChange(PropertyChangeEvent ev) + { + // The default implementation does nothing. + } + +} diff --git a/libjava/classpath/javax/swing/plaf/basic/BasicToggleButtonUI.java b/libjava/classpath/javax/swing/plaf/basic/BasicToggleButtonUI.java new file mode 100644 index 000000000..4550f08e6 --- /dev/null +++ b/libjava/classpath/javax/swing/plaf/basic/BasicToggleButtonUI.java @@ -0,0 +1,134 @@ +/* BasicToggleButtonUI.java + Copyright (C) 2002, 2004, 2005, 2006, 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 java.awt.Font; +import java.awt.Graphics; +import java.awt.Rectangle; + +import javax.swing.AbstractButton; +import javax.swing.JComponent; +import javax.swing.JToggleButton; +import javax.swing.SwingUtilities; +import javax.swing.UIManager; +import javax.swing.plaf.ComponentUI; + +/** + * A UI delegate for the {@link JToggleButton} component. + */ +public class BasicToggleButtonUI extends BasicButtonUI +{ + + /** + * Returns a UI delegate for the specified component. + * + * @param component the component (should be an instance of + * {@link JToggleButton}). + * + * @return An instance of <code>BasicToggleButtonUI</code>. + */ + public static ComponentUI createUI(JComponent component) + { + return new BasicToggleButtonUI(); + } + + /** + * Returns the prefix for entries in the {@link UIManager} defaults table + * (<code>"ToggleButton."</code> in this case). + * + * @return <code>"ToggleButton."</code> + */ + protected String getPropertyPrefix() + { + return "ToggleButton."; + } + + /** + * Paint the component, which is an {@link AbstractButton}, according to + * its current state. + * + * @param g The graphics context to paint with + * @param c The component to paint the state of + */ + public void paint(Graphics g, JComponent c) + { + AbstractButton b = (AbstractButton) c; + + Rectangle tr = new Rectangle(); + Rectangle ir = new Rectangle(); + Rectangle vr = new Rectangle(); + + Font f = c.getFont(); + + g.setFont(f); + + if (b.isBorderPainted()) + SwingUtilities.calculateInnerArea(b, vr); + else + vr = SwingUtilities.getLocalBounds(b); + String text = SwingUtilities.layoutCompoundLabel(c, g.getFontMetrics(f), + b.getText(), currentIcon(b), b.getVerticalAlignment(), + b.getHorizontalAlignment(), b.getVerticalTextPosition(), + b.getHorizontalTextPosition(), vr, ir, tr, b.getIconTextGap() + + defaultTextShiftOffset); + + if ((b.getModel().isArmed() && b.getModel().isPressed()) + || b.isSelected()) + paintButtonPressed(g, b); + + paintIcon(g, b, ir); + if (text != null) + paintText(g, b, tr, text); + if (b.isFocusOwner() && b.isFocusPainted()) + paintFocus(g, b, vr, tr, ir); + } + + /** + * Paints the icon for the toggle button. This delegates to + * {@link BasicButtonUI#paintIcon(Graphics, JComponent, Rectangle)}. + * + * @param g the graphics context + * @param b the button to paint the icon for + * @param iconRect the area allocated for the icon + */ + protected void paintIcon(Graphics g, AbstractButton b, Rectangle iconRect) + { + super.paintIcon(g, b, iconRect); + } +} diff --git a/libjava/classpath/javax/swing/plaf/basic/BasicToolBarSeparatorUI.java b/libjava/classpath/javax/swing/plaf/basic/BasicToolBarSeparatorUI.java new file mode 100644 index 000000000..79cf0b0c2 --- /dev/null +++ b/libjava/classpath/javax/swing/plaf/basic/BasicToolBarSeparatorUI.java @@ -0,0 +1,124 @@ +/* BasicToolBarSeparatorUI.java -- + Copyright (C) 2004 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 java.awt.Dimension; +import java.awt.Graphics; + +import javax.swing.JComponent; +import javax.swing.JSeparator; +import javax.swing.UIManager; +import javax.swing.plaf.ComponentUI; + +/** + * The Basic Look and Feel UI delegate for Separator. + */ +public class BasicToolBarSeparatorUI extends BasicSeparatorUI +{ + private transient Dimension size; + + /** + * Creates a new UI delegate for the given JComponent. + * + * @param c The JComponent to create a delegate for. + * + * @return A new BasicToolBarSeparatorUI. + */ + public static ComponentUI createUI(JComponent c) + { + return new BasicToolBarSeparatorUI(); + } + + /** + * This method installs the defaults that are given by the Basic L&F. + * + * @param s The Separator that is being installed. + */ + protected void installDefaults(JSeparator s) + { + size = UIManager.getDimension("ToolBar.separatorSize"); + } + + /** + * This method does nothing as a Separator is just blank space. + * + * @param g The Graphics object to paint with + * @param c The JComponent to paint. + */ + public void paint(Graphics g, JComponent c) + { + // Do nothing. + } + + /** + * This method returns the preferred size of the JComponent. + * + * @param c The JComponent to measure. + * + * @return The preferred size. + */ + public Dimension getPreferredSize(JComponent c) + { + return size; + } + + /** + * This method returns the minimum size of the JComponent. + * + * @param c The JComponent to measure. + * + * @return The minimum size. + */ + public Dimension getMinimumSize(JComponent c) + { + return size; + } + + /** + * This method returns the maximum size of the JComponent. + * + * @param c The JComponent to measure. + * + * @return The maximum size. + */ + public Dimension getMaximumSize(JComponent c) + { + return size; + } +} diff --git a/libjava/classpath/javax/swing/plaf/basic/BasicToolBarUI.java b/libjava/classpath/javax/swing/plaf/basic/BasicToolBarUI.java new file mode 100644 index 000000000..f5b2b2d1e --- /dev/null +++ b/libjava/classpath/javax/swing/plaf/basic/BasicToolBarUI.java @@ -0,0 +1,1610 @@ +/* BasicToolBarUI.java -- + Copyright (C) 2004, 2005, 2006 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 java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Component; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Insets; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.Window; +import java.awt.event.ActionEvent; +import java.awt.event.ContainerEvent; +import java.awt.event.ContainerListener; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; +import java.awt.event.MouseEvent; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.awt.event.WindowListener; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.Hashtable; + +import javax.swing.AbstractAction; +import javax.swing.AbstractButton; +import javax.swing.Action; +import javax.swing.ActionMap; +import javax.swing.InputMap; +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JDialog; +import javax.swing.JFrame; +import javax.swing.JToolBar; +import javax.swing.KeyStroke; +import javax.swing.LookAndFeel; +import javax.swing.RootPaneContainer; +import javax.swing.SwingConstants; +import javax.swing.SwingUtilities; +import javax.swing.UIManager; +import javax.swing.border.Border; +import javax.swing.border.CompoundBorder; +import javax.swing.event.MouseInputListener; +import javax.swing.plaf.ActionMapUIResource; +import javax.swing.plaf.ComponentUI; +import javax.swing.plaf.ToolBarUI; +import javax.swing.plaf.UIResource; +import javax.swing.plaf.basic.BasicBorders.ButtonBorder; + +/** + * This is the Basic Look and Feel UI class for JToolBar. + */ +public class BasicToolBarUI extends ToolBarUI implements SwingConstants +{ + + /** + * Implements the keyboard actions for JToolBar. + */ + static class ToolBarAction + extends AbstractAction + { + /** + * Performs the action. + */ + public void actionPerformed(ActionEvent event) + { + Object cmd = getValue("__command__"); + JToolBar toolBar = (JToolBar) event.getSource(); + BasicToolBarUI ui = (BasicToolBarUI) toolBar.getUI(); + + if (cmd.equals("navigateRight")) + ui.navigateFocusedComp(EAST); + else if (cmd.equals("navigateLeft")) + ui.navigateFocusedComp(WEST); + else if (cmd.equals("navigateUp")) + ui.navigateFocusedComp(NORTH); + else if (cmd.equals("navigateDown")) + ui.navigateFocusedComp(SOUTH); + else + assert false : "Shouldn't reach here"; + } + } + + /** Static owner of all DragWindows. + * This is package-private to avoid an accessor method. */ + static JFrame owner = new JFrame(); + + /** The border used when the JToolBar is in nonrollover mode. */ + private static Border nonRolloverBorder; + + /** The border used when the JToolBar is in rollover mode. */ + private static Border rolloverBorder; + + /** The last known BorderLayout constraint before floating. */ + protected String constraintBeforeFloating; + + /** The last known orientation of the JToolBar before floating. + * This is package-private to avoid an accessor method. */ + int lastGoodOrientation; + + /** The color of the border when it is dockable. */ + protected Color dockingBorderColor; + + /** The background color of the JToolBar when it is dockable. */ + protected Color dockingColor; + + /** The docking listener responsible for mouse events on the JToolBar. */ + protected MouseInputListener dockingListener; + + /** The window used for dragging the JToolBar. */ + protected BasicToolBarUI.DragWindow dragWindow; + + /** The color of the border when it is not dockable. */ + protected Color floatingBorderColor; + + /** The background color of the JToolBar when it is not dockable. */ + protected Color floatingColor; + + /** The index of the focused component. */ + protected int focusedCompIndex; + + /** The PropertyChangeListener for the JToolBar. */ + protected PropertyChangeListener propertyListener; + + /** The JToolBar this UI delegate is responsible for. */ + protected JToolBar toolBar; + + /** The Container listener for the JToolBar. */ + protected ContainerListener toolBarContListener; + + /** The Focus listener for the JToolBar. */ + protected FocusListener toolBarFocusListener; + + /** + * @deprecated since JDK1.3. + */ + protected KeyStroke leftKey; + + /** + * @deprecated since JDK1.3. + */ + protected KeyStroke rightKey; + + /** + * @deprecated since JDK1.3. + */ + protected KeyStroke upKey; + + /** + * @deprecated since JDK1.3. + */ + protected KeyStroke downKey; + + /** + * The floating window that is responsible for holding the JToolBar when it + * is dragged outside of its original parent. + */ + private transient Window floatFrame; + + /** The original parent of the JToolBar. + * This is package-private to avoid an accessor method. */ + transient Container origParent; + + /** A hashtable of components and their original borders. + * This is package-private to avoid an accessor method. */ + transient Hashtable borders; + + /** A window listener for the floatable frame. */ + private transient WindowListener windowListener; + + /** A set of cached bounds of the JToolBar. + * This is package-private to avoid an accessor method. */ + transient Dimension cachedBounds; + + /** The cached orientation of the JToolBar. + * This is package-private to avoid an accessor method. */ + transient int cachedOrientation; + + /** + * This method creates a new <code>BasicToolBarUI</code> object for the given JToolBar. + */ + public BasicToolBarUI() + { + // Do nothing here. + } + + /** + * This method returns whether the JToolBar can dock at the given position. + * + * @param c The component to try to dock in. + * @param p The position of the mouse cursor relative to the given + * component. + * + * @return Whether the JToolBar can dock. + */ + public boolean canDock(Component c, Point p) + { + return areaOfClick(c, p) != -1; + } + + /** + * This helper method returns the position of the JToolBar if it can dock. + * + * @param c The component to try to dock in. + * @param p The position of the mouse cursor relative to the given + * component. + * + * @return One of the SwingConstants directions or -1 if the JToolBar can't + * dock. + */ + private int areaOfClick(Component c, Point p) + { + // Has to dock in immediate parent, not eventual root container. + Rectangle pBounds = c.getBounds(); + + // XXX: In Sun's implementation, the space the toolbar has to dock is dependent on the size it had last. + Dimension d = toolBar.getSize(); + int limit = Math.min(d.width, d.height); + + // The order of checking is 1. top 2. bottom 3. left 4. right + if (! pBounds.contains(p)) + return -1; + + if (p.y < limit) + return SwingConstants.NORTH; + + if (p.y > (pBounds.height - limit)) + return SwingConstants.SOUTH; + + if (p.x < limit) + return SwingConstants.WEST; + + if (p.x > (pBounds.width - limit)) + return SwingConstants.EAST; + + return -1; + } + + /** + * This method creates a new DockingListener for the JToolBar. + * + * @return A new DockingListener for the JToolBar. + */ + protected MouseInputListener createDockingListener() + { + return new DockingListener(toolBar); + } + + /** + * This method creates a new DragWindow for the given JToolBar. + * + * @param toolbar The JToolBar to create a DragWindow for. + * + * @return A new DragWindow. + */ + protected BasicToolBarUI.DragWindow createDragWindow(JToolBar toolbar) + { + return new DragWindow(); + } + + /** + * This method creates a new floating frame for the JToolBar. By default, + * this UI uses createFloatingWindow instead. This method of creating a + * floating frame is deprecated. + * + * @param toolbar The JToolBar to create a floating frame for. + * + * @return A new floating frame. + */ + protected JFrame createFloatingFrame(JToolBar toolbar) + { + // FIXME: Though deprecated, this should still work. + return null; + } + + /** + * This method creates a new floating window for the JToolBar. This is the + * method used by default to create a floating container for the JToolBar. + * + * @param toolbar The JToolBar to create a floating window for. + * + * @return A new floating window. + */ + protected RootPaneContainer createFloatingWindow(JToolBar toolbar) + { + // This one is used by default though. + return new ToolBarDialog(); + } + + /** + * This method creates a new WindowListener for the JToolBar. + * + * @return A new WindowListener. + */ + protected WindowListener createFrameListener() + { + return new FrameListener(); + } + + /** + * This method creates a new nonRolloverBorder for JButtons when the + * JToolBar's rollover property is set to false. + * + * @return A new NonRolloverBorder. + */ + protected Border createNonRolloverBorder() + { + Border b = UIManager.getBorder("ToolBar.nonrolloverBorder"); + + if (b == null) + { + b = new CompoundBorder( + new ButtonBorder(UIManager.getColor("Button.shadow"), + UIManager.getColor("Button.darkShadow"), + UIManager.getColor("Button.light"), + UIManager.getColor("Button.highlight")), + BasicBorders.getMarginBorder()); + } + + return b; } + + /** + * This method creates a new PropertyChangeListener for the JToolBar. + * + * @return A new PropertyChangeListener. + */ + protected PropertyChangeListener createPropertyListener() + { + return new PropertyListener(); + } + + /** + * This method creates a new rollover border for JButtons when the + * JToolBar's rollover property is set to true. + * + * @return A new rollover border. + */ + protected Border createRolloverBorder() + { + Border b = UIManager.getBorder("ToolBar.rolloverBorder"); + + if (b == null) + { + b = new CompoundBorder( + new ButtonBorder(UIManager.getColor("Button.shadow"), + UIManager.getColor("Button.darkShadow"), + UIManager.getColor("Button.light"), + UIManager.getColor("Button.highlight")), + BasicBorders.getMarginBorder()); + } + + return b; + } + + /** + * This method creates a new Container listener for the JToolBar. + * + * @return A new Container listener. + */ + protected ContainerListener createToolBarContListener() + { + return new ToolBarContListener(); + } + + /** + * This method creates a new FocusListener for the JToolBar. + * + * @return A new FocusListener for the JToolBar. + */ + protected FocusListener createToolBarFocusListener() + { + return new ToolBarFocusListener(); + } + + /** + * This method creates a new UI delegate for the given JComponent. + * + * @param c The JComponent to create a UI delegate for. + * + * @return A new UI delegate. + */ + public static ComponentUI createUI(JComponent c) + { + return new BasicToolBarUI(); + } + + /** + * This method is called to drag the DragWindow around when the JToolBar is + * being dragged around. + * + * @param position The mouse cursor coordinates relative to the JToolBar. + * @param origin The screen position of the JToolBar. + */ + protected void dragTo(Point position, Point origin) + { + int loc = areaOfClick(origParent, + SwingUtilities.convertPoint(toolBar, position, + origParent)); + + if (loc != -1) + { + dragWindow.setBorderColor(dockingBorderColor); + dragWindow.setBackground(dockingColor); + } + else + { + dragWindow.setBorderColor(floatingBorderColor); + dragWindow.setBackground(floatingColor); + } + + int w = 0; + int h = 0; + + boolean tmp = (loc == SwingConstants.NORTH) + || (loc == SwingConstants.SOUTH) || (loc == -1); + + cachedOrientation = toolBar.getOrientation(); + cachedBounds = toolBar.getSize(); + if (((cachedOrientation == SwingConstants.HORIZONTAL) && tmp) + || ((cachedOrientation == VERTICAL) && ! tmp)) + { + w = cachedBounds.width; + h = cachedBounds.height; + } + else + { + w = cachedBounds.height; + h = cachedBounds.width; + } + + Point p = dragWindow.getOffset(); + Insets insets = toolBar.getInsets(); + + dragWindow.setBounds((origin.x + position.x) - p.x + - ((insets.left + insets.right) / 2), + (origin.y + position.y) - p.y + - ((insets.top + insets.bottom) / 2), w, h); + + if (! dragWindow.isVisible()) + dragWindow.show(); + } + + /** + * This method is used at the end of a drag session to place the frame in + * either its original parent as a docked JToolBar or in its floating + * frame. + * + * @param position The position of the mouse cursor relative to the + * JToolBar. + * @param origin The screen position of the JToolBar before the drag session + * started. + */ + protected void floatAt(Point position, Point origin) + { + Point p = new Point(position); + int aoc = areaOfClick(origParent, + SwingUtilities.convertPoint(toolBar, p, origParent)); + + Container oldParent = toolBar.getParent(); + + oldParent.remove(toolBar); + oldParent.doLayout(); + oldParent.repaint(); + + Container newParent; + + if (aoc == -1) + newParent = ((RootPaneContainer) floatFrame).getContentPane(); + else + { + floatFrame.hide(); + newParent = origParent; + } + + String constraint; + switch (aoc) + { + case SwingConstants.EAST: + constraint = BorderLayout.EAST; + break; + case SwingConstants.NORTH: + constraint = BorderLayout.NORTH; + break; + case SwingConstants.SOUTH: + constraint = BorderLayout.SOUTH; + break; + case SwingConstants.WEST: + constraint = BorderLayout.WEST; + break; + default: + constraint = BorderLayout.CENTER; + break; + } + + int newOrientation = SwingConstants.HORIZONTAL; + if ((aoc != -1) + && ((aoc == SwingConstants.EAST) || (aoc == SwingConstants.WEST))) + newOrientation = SwingConstants.VERTICAL; + + if (aoc != -1) + { + constraintBeforeFloating = constraint; + lastGoodOrientation = newOrientation; + } + + newParent.add(toolBar, constraint); + + setFloating(aoc == -1, null); + toolBar.setOrientation(newOrientation); + + Insets insets = floatFrame.getInsets(); + Dimension dims = toolBar.getPreferredSize(); + p = dragWindow.getOffset(); + setFloatingLocation((position.x + origin.x) - p.x + - ((insets.left + insets.right) / 2), + (position.y + origin.y) - p.y + - ((insets.top + insets.bottom) / 2)); + + if (aoc == -1) + { + floatFrame.pack(); + floatFrame.setSize(dims.width + insets.left + insets.right, + dims.height + insets.top + insets.bottom); + floatFrame.show(); + } + + newParent.invalidate(); + newParent.validate(); + newParent.repaint(); + } + + /** + * This method returns the docking color. + * + * @return The docking color. + */ + public Color getDockingColor() + { + return dockingColor; + } + + /** + * This method returns the Color which is displayed when over a floating + * area. + * + * @return The color which is displayed when over a floating area. + */ + public Color getFloatingColor() + { + return floatingColor; + } + + /** + * This method returns the maximum size of the given JComponent for this UI. + * + * @param c The JComponent to find the maximum size for. + * + * @return The maximum size for this UI. + */ + public Dimension getMaximumSize(JComponent c) + { + return getPreferredSize(c); + } + + /** + * This method returns the minimum size of the given JComponent for this UI. + * + * @param c The JComponent to find a minimum size for. + * + * @return The minimum size for this UI. + */ + public Dimension getMinimumSize(JComponent c) + { + return getPreferredSize(c); + } + + /** + * This method installs the needed components for the JToolBar. + */ + protected void installComponents() + { + floatFrame = (Window) createFloatingWindow(toolBar); + + dragWindow = createDragWindow(toolBar); + + nonRolloverBorder = createNonRolloverBorder(); + rolloverBorder = createRolloverBorder(); + + borders = new Hashtable(); + setRolloverBorders(toolBar.isRollover()); + + fillHashtable(); + } + + /** + * This method installs the defaults as specified by the look and feel. + */ + protected void installDefaults() + { + LookAndFeel.installBorder(toolBar, "ToolBar.border"); + LookAndFeel.installColorsAndFont(toolBar, "ToolBar.background", + "ToolBar.foreground", "ToolBar.font"); + + dockingBorderColor = UIManager.getColor("ToolBar.dockingForeground"); + dockingColor = UIManager.getColor("ToolBar.dockingBackground"); + + floatingBorderColor = UIManager.getColor("ToolBar.floatingForeground"); + floatingColor = UIManager.getColor("ToolBar.floatingBackground"); + } + + /** + * This method installs the keyboard actions for the JToolBar as specified + * by the look and feel. + */ + protected void installKeyboardActions() + { + // Install the input map. + InputMap inputMap = + (InputMap) SharedUIDefaults.get("ToolBar.ancestorInputMap"); + SwingUtilities.replaceUIInputMap(toolBar, + JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, + inputMap); + + // FIXME: The JDK uses a LazyActionMap for parentActionMap + SwingUtilities.replaceUIActionMap(toolBar, getActionMap()); + } + + /** + * Fetches the action map from the UI defaults, or create a new one + * if the action map hasn't been initialized. + * + * @return the action map + */ + private ActionMap getActionMap() + { + ActionMap am = (ActionMap) UIManager.get("ToolBar.actionMap"); + if (am == null) + { + am = createDefaultActions(); + UIManager.getLookAndFeelDefaults().put("ToolBar.actionMap", am); + } + return am; + } + + private ActionMap createDefaultActions() + { + ActionMapUIResource am = new ActionMapUIResource(); + Action action = new ToolBarAction(); + + am.put("navigateLeft", action); + am.put("navigateRight", action); + am.put("navigateUp", action); + am.put("navigateDown", action); + + return am; + } + + /** + * This method installs listeners for the JToolBar. + */ + protected void installListeners() + { + dockingListener = createDockingListener(); + toolBar.addMouseListener(dockingListener); + toolBar.addMouseMotionListener(dockingListener); + + propertyListener = createPropertyListener(); + toolBar.addPropertyChangeListener(propertyListener); + + toolBarContListener = createToolBarContListener(); + toolBar.addContainerListener(toolBarContListener); + + windowListener = createFrameListener(); + floatFrame.addWindowListener(windowListener); + + toolBarFocusListener = createToolBarFocusListener(); + if (toolBarFocusListener != null) + { + int count = toolBar.getComponentCount(); + for (int i = 0; i < count; i++) + toolBar.getComponent(i).addFocusListener(toolBarFocusListener); + } + } + + /** + * This method installs non rollover borders for each component inside the + * given JComponent. + * + * @param c The JComponent whose children need to have non rollover borders + * installed. + */ + protected void installNonRolloverBorders(JComponent c) + { + Component[] components = toolBar.getComponents(); + + for (int i = 0; i < components.length; i++) + setBorderToNonRollover(components[i]); + } + + /** + * This method installs normal (or their original) borders for each + * component inside the given JComponent. + * + * @param c The JComponent whose children need to have their original + * borders installed. + */ + protected void installNormalBorders(JComponent c) + { + Component[] components = toolBar.getComponents(); + + for (int i = 0; i < components.length; i++) + setBorderToNormal(components[i]); + } + + /** + * This method install rollover borders for each component inside the given + * JComponent. + * + * @param c The JComponent whose children need to have rollover borders + * installed. + */ + protected void installRolloverBorders(JComponent c) + { + Component[] components = toolBar.getComponents(); + + for (int i = 0; i < components.length; i++) + setBorderToRollover(components[i]); + } + + /** + * This method fills the borders hashtable with a list of components that + * are JButtons and their borders. + */ + private void fillHashtable() + { + Component[] c = toolBar.getComponents(); + + for (int i = 0; i < c.length; i++) + { + if (c[i] instanceof JButton) + { + // Don't really care about anything other than JButtons + JButton b = (JButton) c[i]; + + if (b.getBorder() != null) + borders.put(b, b.getBorder()); + } + } + } + + /** + * This method installs the UI for the given JComponent. + * + * @param c The JComponent to install a UI for. + */ + public void installUI(JComponent c) + { + super.installUI(c); + + if (c instanceof JToolBar) + { + toolBar = (JToolBar) c; + installDefaults(); + installComponents(); + installListeners(); + installKeyboardActions(); + } + } + + /** + * This method returns whether the JToolBar is floating. + * + * @return Whether the JToolBar is floating. + */ + public boolean isFloating() + { + return floatFrame.isVisible(); + } + + /** + * This method returns whether rollover borders have been set. + * + * @return Whether rollover borders have been set. + */ + public boolean isRolloverBorders() + { + return toolBar.isRollover(); + } + + /** + * This method navigates in the given direction giving focus to the next + * component in the given direction. + * + * @param direction The direction to give focus to. + */ + protected void navigateFocusedComp(int direction) + { + int count = toolBar.getComponentCount(); + switch (direction) + { + case EAST: + case SOUTH: + if (focusedCompIndex >= 0 && focusedCompIndex < count) + { + int i = focusedCompIndex + 1; + boolean focusRequested = false; + // Find component to focus and request focus on it. + while (i != focusedCompIndex && ! focusRequested) + { + if (i >= count) + i = 0; + Component comp = toolBar.getComponentAtIndex(i++); + if (comp != null && comp.isFocusable() + && comp.isEnabled()) + { + comp.requestFocus(); + focusRequested = true; + } + } + } + break; + case WEST: + case NORTH: + if (focusedCompIndex >= 0 && focusedCompIndex < count) + { + int i = focusedCompIndex - 1; + boolean focusRequested = false; + // Find component to focus and request focus on it. + while (i != focusedCompIndex && ! focusRequested) + { + if (i < 0) + i = count - 1; + Component comp = toolBar.getComponentAtIndex(i--); + if (comp != null && comp.isFocusable() + && comp.isEnabled()) + { + comp.requestFocus(); + focusRequested = true; + } + } + } + break; + default: + break; + } + } + + /** + * This method sets the border of the given component to a non rollover + * border. + * + * @param c The Component whose border needs to be set. + */ + protected void setBorderToNonRollover(Component c) + { + if (c instanceof AbstractButton) + { + AbstractButton b = (AbstractButton) c; + b.setRolloverEnabled(false); + + // Save old border in hashtable. + if (b.getBorder() != null) + borders.put(b, b.getBorder()); + + b.setBorder(nonRolloverBorder); + } + } + + /** + * This method sets the border of the given component to its original value. + * + * @param c The Component whose border needs to be set. + */ + protected void setBorderToNormal(Component c) + { + if (c instanceof AbstractButton) + { + AbstractButton b = (AbstractButton) c; + b.setRolloverEnabled(true); + b.setBorder((Border) borders.remove(b)); + } + } + + /** + * This method sets the border of the given component to a rollover border. + * + * @param c The Component whose border needs to be set. + */ + protected void setBorderToRollover(Component c) + { + if (c instanceof AbstractButton) + { + AbstractButton b = (AbstractButton) c; + b.setRolloverEnabled(false); + + // Save old border in hashtable. + if (b.getBorder() != null) + borders.put(b, b.getBorder()); + + b.setBorder(rolloverBorder); + } + } + + /** + * This method sets the docking color. + * + * @param c The docking color. + */ + public void setDockingColor(Color c) + { + dockingColor = c; + } + + /** + * This method sets the floating property for the JToolBar. + * + * @param b Whether the JToolBar is floating. + * @param p FIXME + */ + public void setFloating(boolean b, Point p) + { + // FIXME: use p for something. It's not location + // since we already have setFloatingLocation. + floatFrame.setVisible(b); + } + + /** + * This method sets the color displayed when the JToolBar is not in a + * dockable area. + * + * @param c The floating color. + */ + public void setFloatingColor(Color c) + { + floatingColor = c; + } + + /** + * This method sets the floating location of the JToolBar. + * + * @param x The x coordinate for the floating frame. + * @param y The y coordinate for the floating frame. + */ + public void setFloatingLocation(int x, int y) + { + // x,y are the coordinates of the new JFrame created to store the toolbar + // XXX: The floating location is bogus is not floating. + floatFrame.setLocation(x, y); + floatFrame.invalidate(); + floatFrame.validate(); + floatFrame.repaint(); + } + + /** + * This is a convenience method for changing the orientation of the + * JToolBar. + * + * @param orientation The new orientation. + */ + public void setOrientation(int orientation) + { + toolBar.setOrientation(orientation); + } + + /** + * This method changes the child components to have rollover borders if the + * given parameter is true. Otherwise, the components are set to have non + * rollover borders. + * + * @param rollover Whether the children will have rollover borders. + */ + public void setRolloverBorders(boolean rollover) + { + if (rollover) + installRolloverBorders(toolBar); + else + installNonRolloverBorders(toolBar); + } + + /** + * This method uninstall UI installed components from the JToolBar. + */ + protected void uninstallComponents() + { + installNormalBorders(toolBar); + borders = null; + cachedBounds = null; + + floatFrame = null; + dragWindow = null; + } + + /** + * This method removes the defaults installed by the Look and Feel. + */ + protected void uninstallDefaults() + { + toolBar.setBackground(null); + toolBar.setForeground(null); + toolBar.setFont(null); + + dockingBorderColor = null; + dockingColor = null; + floatingBorderColor = null; + floatingColor = null; + } + + /** + * This method uninstalls keyboard actions installed by the UI. + */ + protected void uninstallKeyboardActions() + { + SwingUtilities.replaceUIInputMap(toolBar, JComponent. + WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, null); + SwingUtilities.replaceUIActionMap(toolBar, null); + } + + /** + * This method uninstalls listeners installed by the UI. + */ + protected void uninstallListeners() + { + if (toolBarFocusListener != null) + { + int count = toolBar.getComponentCount(); + for (int i = 0; i < count; i++) + toolBar.getComponent(i).removeFocusListener(toolBarFocusListener); + toolBarFocusListener = null; + } + + floatFrame.removeWindowListener(windowListener); + windowListener = null; + + toolBar.removeContainerListener(toolBarContListener); + toolBarContListener = null; + + toolBar.removeMouseMotionListener(dockingListener); + toolBar.removeMouseListener(dockingListener); + dockingListener = null; + } + + /** + * This method uninstalls the UI. + * + * @param c The JComponent that is having this UI removed. + */ + public void uninstallUI(JComponent c) + { + uninstallKeyboardActions(); + uninstallListeners(); + uninstallComponents(); + uninstallDefaults(); + toolBar = null; + } + + /** + * This is the MouseHandler class that allows the user to drag the JToolBar + * in and out of the parent and dock it if it can. + */ + public class DockingListener implements MouseInputListener + { + /** Whether the JToolBar is being dragged. */ + protected boolean isDragging; + + /** + * The origin point. This point is saved from the beginning press and is + * used until the end of the drag session. + */ + protected Point origin; + + /** The JToolBar being dragged. */ + protected JToolBar toolBar; + + /** + * Creates a new DockingListener object. + * + * @param t The JToolBar this DockingListener is being used for. + */ + public DockingListener(JToolBar t) + { + toolBar = t; + } + + /** + * This method is called when the mouse is clicked. + * + * @param e The MouseEvent. + */ + public void mouseClicked(MouseEvent e) + { + // Nothing to do here. + } + + /** + * This method is called when the mouse is dragged. It delegates the drag + * painting to the dragTo method. + * + * @param e The MouseEvent. + */ + public void mouseDragged(MouseEvent e) + { + if (isDragging) + dragTo(e.getPoint(), origin); + } + + /** + * This method is called when the mouse enters the JToolBar. + * + * @param e The MouseEvent. + */ + public void mouseEntered(MouseEvent e) + { + // Nothing to do here. + } + + /** + * This method is called when the mouse exits the JToolBar. + * + * @param e The MouseEvent. + */ + public void mouseExited(MouseEvent e) + { + // Nothing to do here. + } + + /** + * This method is called when the mouse is moved in the JToolBar. + * + * @param e The MouseEvent. + */ + public void mouseMoved(MouseEvent e) + { + // Nothing to do here. + } + + /** + * This method is called when the mouse is pressed in the JToolBar. If the + * press doesn't occur in a place where it causes the JToolBar to be + * dragged, it returns. Otherwise, it starts a drag session. + * + * @param e The MouseEvent. + */ + public void mousePressed(MouseEvent e) + { + if (! toolBar.isFloatable()) + return; + + Point ssd = e.getPoint(); + Insets insets = toolBar.getInsets(); + + // Verify that this click occurs in the top inset. + if (toolBar.getOrientation() == SwingConstants.HORIZONTAL) + { + if (e.getX() > insets.left) + return; + } + else + { + if (e.getY() > insets.top) + return; + } + + origin = new Point(0, 0); + if (toolBar.isShowing()) + SwingUtilities.convertPointToScreen(ssd, toolBar); + + if (! (SwingUtilities.getAncestorOfClass(Window.class, toolBar) instanceof UIResource)) + // Need to know who keeps the toolBar if it gets dragged back into it. + origParent = toolBar.getParent(); + + if (toolBar.isShowing()) + SwingUtilities.convertPointToScreen(origin, toolBar); + + isDragging = true; + + if (dragWindow != null) + dragWindow.setOffset(new Point(cachedBounds.width / 2, + cachedBounds.height / 2)); + + dragTo(e.getPoint(), origin); + } + + /** + * This method is called when the mouse is released from the JToolBar. + * + * @param e The MouseEvent. + */ + public void mouseReleased(MouseEvent e) + { + if (! isDragging || ! toolBar.isFloatable()) + return; + + isDragging = false; + floatAt(e.getPoint(), origin); + dragWindow.hide(); + } + } + + /** + * This is the window that appears when the JToolBar is being dragged + * around. + */ + protected class DragWindow extends Window + { + /** + * The current border color. It changes depending on whether the JToolBar + * is over a place that allows it to dock. + */ + private Color borderColor; + + /** The between the mouse and the top left corner of the window. */ + private Point offset; + + /** + * Creates a new DragWindow object. + * This is package-private to avoid an accessor method. + */ + DragWindow() + { + super(owner); + } + + /** + * The color that the border should be. + * + * @return The border color. + */ + public Color getBorderColor() + { + if (borderColor == null) + return Color.BLACK; + + return borderColor; + } + + /** + * This method returns the insets for the DragWindow. + * + * @return The insets for the DragWindow. + */ + public Insets getInsets() + { + // This window has no decorations, so insets are empty. + return new Insets(0, 0, 0, 0); + } + + /** + * This method returns the mouse offset from the top left corner of the + * DragWindow. + * + * @return The mouse offset. + */ + public Point getOffset() + { + return offset; + } + + /** + * This method paints the DragWindow. + * + * @param g The Graphics object to paint with. + */ + public void paint(Graphics g) + { + // No visiting children necessary. + Color saved = g.getColor(); + Rectangle b = getBounds(); + + g.setColor(getBorderColor()); + g.drawRect(0, 0, b.width - 1, b.height - 1); + + g.setColor(saved); + } + + /** + * This method changes the border color. + * + * @param c The new border color. + */ + public void setBorderColor(Color c) + { + borderColor = c; + } + + /** + * This method changes the mouse offset. + * + * @param p The new mouse offset. + */ + public void setOffset(Point p) + { + offset = p; + } + + /** + * Sets the orientation of the toolbar and the + * drag window. + * + * @param o - the new orientation of the toolbar and drag + * window. + */ + public void setOrientation(int o) + { + toolBar.setOrientation(o); + if (dragWindow != null) + dragWindow.setOrientation(o); + } + } + + /** + * This helper class listens for Window events from the floatable window and + * if it is closed, returns the JToolBar to the last known good location. + */ + protected class FrameListener extends WindowAdapter + { + /** + * This method is called when the floating window is closed. + * + * @param e The WindowEvent. + */ + public void windowClosing(WindowEvent e) + { + Container parent = toolBar.getParent(); + parent.remove(toolBar); + + if (origParent != null) + { + origParent.add(toolBar, + (constraintBeforeFloating != null) + ? constraintBeforeFloating : BorderLayout.NORTH); + toolBar.setOrientation(lastGoodOrientation); + } + + origParent.invalidate(); + origParent.validate(); + origParent.repaint(); + } + } + + /** + * This helper class listens for PropertyChangeEvents from the JToolBar. + */ + protected class PropertyListener implements PropertyChangeListener + { + /** + * This method is called when a property from the JToolBar is changed. + * + * @param e The PropertyChangeEvent. + */ + public void propertyChange(PropertyChangeEvent e) + { + // FIXME: need name properties so can change floatFrame title. + if (e.getPropertyName().equals("rollover") && toolBar != null) + setRolloverBorders(toolBar.isRollover()); + } + } + + /** + * This helper class listens for components added to and removed from the + * JToolBar. + */ + protected class ToolBarContListener implements ContainerListener + { + /** + * This method is responsible for setting rollover or non rollover for new + * buttons added to the JToolBar. + * + * @param e The ContainerEvent. + */ + public void componentAdded(ContainerEvent e) + { + if (e.getChild() instanceof JButton) + { + JButton b = (JButton) e.getChild(); + + if (b.getBorder() != null) + borders.put(b, b.getBorder()); + } + + if (isRolloverBorders()) + setBorderToRollover(e.getChild()); + else + setBorderToNonRollover(e.getChild()); + + cachedBounds = toolBar.getPreferredSize(); + cachedOrientation = toolBar.getOrientation(); + + Component c = e.getChild(); + if (toolBarFocusListener != null) + c.addFocusListener(toolBarFocusListener); + } + + /** + * This method is responsible for giving the child components their + * original borders when they are removed. + * + * @param e The ContainerEvent. + */ + public void componentRemoved(ContainerEvent e) + { + setBorderToNormal(e.getChild()); + cachedBounds = toolBar.getPreferredSize(); + cachedOrientation = toolBar.getOrientation(); + + Component c = e.getChild(); + if (toolBarFocusListener != null) + c.removeFocusListener(toolBarFocusListener); + } + } + + /** + * This is the floating window that is returned when getFloatingWindow is + * called. + */ + private class ToolBarDialog extends JDialog implements UIResource + { + /** + * Creates a new ToolBarDialog object with the name given by the JToolBar. + */ + public ToolBarDialog() + { + super(); + setName((toolBar.getName() != null) ? toolBar.getName() : ""); + } + } + + /** + * DOCUMENT ME! + */ + protected class ToolBarFocusListener implements FocusListener + { + /** + * Creates a new ToolBarFocusListener object. + */ + protected ToolBarFocusListener() + { + // Nothing to do here. + } + + /** + * Receives notification when the toolbar or one of it's component + * receives the keyboard input focus. + * + * @param e the focus event + */ + public void focusGained(FocusEvent e) + { + Component c = e.getComponent(); + focusedCompIndex = toolBar.getComponentIndex(c); + } + + /** + * Receives notification when the toolbar or one of it's component + * looses the keyboard input focus. + * + * @param e the focus event + */ + public void focusLost(FocusEvent e) + { + // Do nothing here. + } + } + + /** + * This helper class acts as the border for the JToolBar. + */ + private static class ToolBarBorder implements Border + { + /** The size of the larger, draggable side of the border. */ + private static final int offset = 10; + + /** The other sides. */ + private static final int regular = 2; + + /** + * This method returns the border insets for the JToolBar. + * + * @param c The Component to find insets for. + * + * @return The border insets. + */ + public Insets getBorderInsets(Component c) + { + if (c instanceof JToolBar) + { + JToolBar tb = (JToolBar) c; + int orientation = tb.getOrientation(); + + if (! tb.isFloatable()) + return new Insets(regular, regular, regular, regular); + else if (orientation == SwingConstants.HORIZONTAL) + return new Insets(regular, offset, regular, regular); + else + return new Insets(offset, regular, regular, regular); + } + + return new Insets(0, 0, 0, 0); + } + + /** + * This method returns whether the border is opaque. + * + * @return Whether the border is opaque. + */ + public boolean isBorderOpaque() + { + return false; + } + + /** + * This method paints the ribbed area of the border. + * + * @param g The Graphics object to paint with. + * @param x The x coordinate of the area. + * @param y The y coordinate of the area. + * @param w The width of the area. + * @param h The height of the area. + * @param size The size of the bump. + * @param c The color of the bumps. + */ + private void paintBumps(Graphics g, int x, int y, int w, int h, int size, + Color c) + { + Color saved = g.getColor(); + g.setColor(c); + + int hgap = 2 * size; + int vgap = 4 * size; + int count = 0; + + for (int i = x; i < (w + x); i += hgap) + for (int j = ((count++ % 2) == 0) ? y : (y + (2 * size)); j < (h + y); + j += vgap) + g.fillRect(i, j, size, size); + + g.setColor(saved); + } + + /** + * This method paints the border around the given Component. + * + * @param c The Component whose border is being painted. + * @param g The Graphics object to paint with. + * @param x The x coordinate of the component. + * @param y The y coordinate of the component. + * @param width The width of the component. + * @param height The height of the component. + */ + public void paintBorder(Component c, Graphics g, int x, int y, int width, + int height) + { + if (c instanceof JToolBar) + { + JToolBar tb = (JToolBar) c; + + int orientation = tb.getOrientation(); + + if (orientation == SwingConstants.HORIZONTAL) + { + paintBumps(g, x, y, offset, height, 1, Color.WHITE); + paintBumps(g, x + 1, y + 1, offset - 1, height - 1, 1, Color.GRAY); + } + else + { + paintBumps(g, x, y, width, offset, 1, Color.WHITE); + paintBumps(g, x + 1, y + 1, width - 1, offset - 1, 1, Color.GRAY); + } + } + } + } +} diff --git a/libjava/classpath/javax/swing/plaf/basic/BasicToolTipUI.java b/libjava/classpath/javax/swing/plaf/basic/BasicToolTipUI.java new file mode 100644 index 000000000..37c084bb8 --- /dev/null +++ b/libjava/classpath/javax/swing/plaf/basic/BasicToolTipUI.java @@ -0,0 +1,292 @@ +/* BasicToolTipUI.java -- + Copyright (C) 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 java.awt.Color; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.FontMetrics; +import java.awt.Graphics; +import java.awt.Insets; +import java.awt.Rectangle; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; + +import javax.swing.JComponent; +import javax.swing.JToolTip; +import javax.swing.LookAndFeel; +import javax.swing.plaf.ComponentUI; +import javax.swing.plaf.ToolTipUI; +import javax.swing.text.View; + +/** + * This is the Basic Look and Feel UI class for JToolTip. + */ +public class BasicToolTipUI extends ToolTipUI +{ + + /** + * Receives notification when a property of the JToolTip changes. + * This updates the HTML renderer if appropriate. + */ + private class PropertyChangeHandler + implements PropertyChangeListener + { + + public void propertyChange(PropertyChangeEvent e) + { + String prop = e.getPropertyName(); + if (prop.equals("tiptext") || prop.equals("font") + || prop.equals("foreground")) + { + JToolTip tip = (JToolTip) e.getSource(); + String text = tip.getTipText(); + BasicHTML.updateRenderer(tip, text); + } + } + + } + + /** The shared instance of BasicToolTipUI used for all ToolTips. */ + private static BasicToolTipUI shared; + + /** The tooltip's text */ + private String text; + + /** + * Handles property changes. + */ + private PropertyChangeListener propertyChangeHandler; + + /** + * Creates a new BasicToolTipUI object. + */ + public BasicToolTipUI() + { + super(); + } + + /** + * This method creates a new BasicToolTip UI for the given + * JComponent. + * + * @param c The JComponent to create a UI for. + * + * @return A BasicToolTipUI that can be used by the given JComponent. + */ + public static ComponentUI createUI(JComponent c) + { + if (shared == null) + shared = new BasicToolTipUI(); + return shared; + } + + /** + * This method returns the msximum size of the given JComponent. + * + * @param c The JComponent to find a maximum size for. + * + * @return The maximum size. + */ + public Dimension getMaximumSize(JComponent c) + { + Dimension d = getPreferredSize(c); + View view = (View) c.getClientProperty(BasicHTML.propertyKey); + if (view != null) + d.width += view.getMaximumSpan(View.X_AXIS) + - view.getPreferredSpan(View.X_AXIS); + return d; + } + + /** + * This method returns the minimum size of the given JComponent. + * + * @param c The JComponent to find a minimum size for. + * + * @return The minimum size. + */ + public Dimension getMinimumSize(JComponent c) + { + Dimension d = getPreferredSize(c); + View view = (View) c.getClientProperty(BasicHTML.propertyKey); + if (view != null) + d.width -= view.getPreferredSpan(View.X_AXIS) + - view.getMinimumSpan(View.X_AXIS); + return d; + } + + /** + * This method returns the preferred size of the given JComponent. + * + * @param c The JComponent to find a preferred size for. + * + * @return The preferred size. + */ + public Dimension getPreferredSize(JComponent c) + { + JToolTip tip = (JToolTip) c; + String str = tip.getTipText(); + FontMetrics fm = c.getFontMetrics(c.getFont()); + Insets i = c.getInsets(); + Dimension d = new Dimension(i.left + i.right, i.top + i.bottom); + if (str != null && ! str.equals("")) + { + View view = (View) c.getClientProperty(BasicHTML.propertyKey); + if (view != null) + { + d.width += (int) view.getPreferredSpan(View.X_AXIS); + d.height += (int) view.getPreferredSpan(View.Y_AXIS); + } + else + { + d.width += fm.stringWidth(str) + 6; + d.height += fm.getHeight(); + } + } + return d; + } + + /** + * This method installs the defaults for the given JComponent. + * + * @param c The JComponent to install defaults for. + */ + protected void installDefaults(JComponent c) + { + LookAndFeel.installColorsAndFont(c, "ToolTip.background", + "ToolTip.foreground", "ToolTip.font"); + LookAndFeel.installBorder(c, "ToolTip.border"); + } + + /** + * This method installs the listeners for the given JComponent. + * + * @param c The JComponent to install listeners for. + */ + protected void installListeners(JComponent c) + { + propertyChangeHandler = new PropertyChangeHandler(); + c.addPropertyChangeListener(propertyChangeHandler); + } + + /** + * This method installs the UI for the given JComponent. + * + * @param c The JComponent to install the UI for. + */ + public void installUI(JComponent c) + { + c.setOpaque(true); + installDefaults(c); + BasicHTML.updateRenderer(c, ((JToolTip) c).getTipText()); + installListeners(c); + } + + /** + * This method paints the given JComponent with the given Graphics object. + * + * @param g The Graphics object to paint with. + * @param c The JComponent to paint. + */ + public void paint(Graphics g, JComponent c) + { + JToolTip tip = (JToolTip) c; + + String text = tip.getTipText(); + Font font = c.getFont(); + FontMetrics fm = c.getFontMetrics(font); + int ascent = fm.getAscent(); + Insets i = c.getInsets(); + Dimension size = c.getSize(); + Rectangle paintR = new Rectangle(i.left, i.top, + size.width - i.left - i.right, + size.height - i.top - i.bottom); + Color saved = g.getColor(); + Font oldFont = g.getFont(); + g.setColor(Color.BLACK); + + View view = (View) c.getClientProperty(BasicHTML.propertyKey); + if (view != null) + view.paint(g, paintR); + else + g.drawString(text, paintR.x + 3, paintR.y + ascent); + + g.setFont(oldFont); + g.setColor(saved); + } + + /** + * This method uninstalls the defaults for the given JComponent. + * + * @param c The JComponent to uninstall defaults for. + */ + protected void uninstallDefaults(JComponent c) + { + c.setForeground(null); + c.setBackground(null); + c.setFont(null); + c.setBorder(null); + } + + /** + * This method uninstalls listeners for the given JComponent. + * + * @param c The JComponent to uninstall listeners for. + */ + protected void uninstallListeners(JComponent c) + { + if (propertyChangeHandler != null) + { + c.removePropertyChangeListener(propertyChangeHandler); + propertyChangeHandler = null; + } + } + + /** + * This method uninstalls the UI for the given JComponent. + * + * @param c The JComponent to uninstall. + */ + public void uninstallUI(JComponent c) + { + uninstallDefaults(c); + BasicHTML.updateRenderer(c, ""); + uninstallListeners(c); + } +} diff --git a/libjava/classpath/javax/swing/plaf/basic/BasicTreeUI.java b/libjava/classpath/javax/swing/plaf/basic/BasicTreeUI.java new file mode 100644 index 000000000..af61a422a --- /dev/null +++ b/libjava/classpath/javax/swing/plaf/basic/BasicTreeUI.java @@ -0,0 +1,3939 @@ +/* BasicTreeUI.java -- + Copyright (C) 2002, 2004, 2005, 2006, 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.javax.swing.tree.GnuPath; + +import java.awt.Color; +import java.awt.Component; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Insets; +import java.awt.Label; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.ComponentAdapter; +import java.awt.event.ComponentEvent; +import java.awt.event.ComponentListener; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; +import java.awt.event.InputEvent; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.awt.event.MouseMotionListener; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.Enumeration; +import java.util.Hashtable; + +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.ActionMap; +import javax.swing.CellRendererPane; +import javax.swing.Icon; +import javax.swing.InputMap; +import javax.swing.JComponent; +import javax.swing.JScrollBar; +import javax.swing.JScrollPane; +import javax.swing.JTree; +import javax.swing.LookAndFeel; +import javax.swing.SwingUtilities; +import javax.swing.Timer; +import javax.swing.UIManager; +import javax.swing.event.CellEditorListener; +import javax.swing.event.ChangeEvent; +import javax.swing.event.MouseInputListener; +import javax.swing.event.TreeExpansionEvent; +import javax.swing.event.TreeExpansionListener; +import javax.swing.event.TreeModelEvent; +import javax.swing.event.TreeModelListener; +import javax.swing.event.TreeSelectionEvent; +import javax.swing.event.TreeSelectionListener; +import javax.swing.plaf.ActionMapUIResource; +import javax.swing.plaf.ComponentUI; +import javax.swing.plaf.TreeUI; +import javax.swing.tree.AbstractLayoutCache; +import javax.swing.tree.DefaultTreeCellEditor; +import javax.swing.tree.DefaultTreeCellRenderer; +import javax.swing.tree.TreeCellEditor; +import javax.swing.tree.TreeCellRenderer; +import javax.swing.tree.TreeModel; +import javax.swing.tree.TreeNode; +import javax.swing.tree.TreePath; +import javax.swing.tree.TreeSelectionModel; +import javax.swing.tree.VariableHeightLayoutCache; + +/** + * A delegate providing the user interface for <code>JTree</code> according to + * the Basic look and feel. + * + * @see javax.swing.JTree + * @author Lillian Angel (langel@redhat.com) + * @author Sascha Brawer (brawer@dandelis.ch) + * @author Audrius Meskauskas (audriusa@bioinformatics.org) + */ +public class BasicTreeUI + extends TreeUI +{ + /** + * The tree cell editing may be started by the single mouse click on the + * selected cell. To separate it from the double mouse click, the editing + * session starts after this time (in ms) after that single click, and only no + * other clicks were performed during that time. + */ + static int WAIT_TILL_EDITING = 900; + + /** Collapse Icon for the tree. */ + protected transient Icon collapsedIcon; + + /** Expanded Icon for the tree. */ + protected transient Icon expandedIcon; + + /** Distance between left margin and where vertical dashes will be drawn. */ + protected int leftChildIndent; + + /** + * Distance between leftChildIndent and where cell contents will be drawn. + */ + protected int rightChildIndent; + + /** + * Total fistance that will be indented. The sum of leftChildIndent and + * rightChildIndent . + */ + protected int totalChildIndent; + + /** Index of the row that was last selected. */ + protected int lastSelectedRow; + + /** Component that we're going to be drawing onto. */ + protected JTree tree; + + /** Renderer that is being used to do the actual cell drawing. */ + protected transient TreeCellRenderer currentCellRenderer; + + /** + * Set to true if the renderer that is currently in the tree was created by + * this instance. + */ + protected boolean createdRenderer; + + /** Editor for the tree. */ + protected transient TreeCellEditor cellEditor; + + /** + * Set to true if editor that is currently in the tree was created by this + * instance. + */ + protected boolean createdCellEditor; + + /** + * Set to false when editing and shouldSelectCall() returns true meaning the + * node should be selected before editing, used in completeEditing. + * GNU Classpath editing is implemented differently, so this value is not + * actually read anywhere. However it is always set correctly to maintain + * interoperability with the derived classes that read this field. + */ + protected boolean stopEditingInCompleteEditing; + + /** Used to paint the TreeCellRenderer. */ + protected CellRendererPane rendererPane; + + /** Size needed to completely display all the nodes. */ + protected Dimension preferredSize; + + /** Minimum size needed to completely display all the nodes. */ + protected Dimension preferredMinSize; + + /** Is the preferredSize valid? */ + protected boolean validCachedPreferredSize; + + /** Object responsible for handling sizing and expanded issues. */ + protected AbstractLayoutCache treeState; + + /** Used for minimizing the drawing of vertical lines. */ + protected Hashtable<TreePath, Boolean> drawingCache; + + /** + * True if doing optimizations for a largeModel. Subclasses that don't support + * this may wish to override createLayoutCache to not return a + * FixedHeightLayoutCache instance. + */ + protected boolean largeModel; + + /** Responsible for telling the TreeState the size needed for a node. */ + protected AbstractLayoutCache.NodeDimensions nodeDimensions; + + /** Used to determine what to display. */ + protected TreeModel treeModel; + + /** Model maintaining the selection. */ + protected TreeSelectionModel treeSelectionModel; + + /** + * How much the depth should be offset to properly calculate x locations. This + * is based on whether or not the root is visible, and if the root handles are + * visible. + */ + protected int depthOffset; + + /** + * When editing, this will be the Component that is doing the actual editing. + */ + protected Component editingComponent; + + /** Path that is being edited. */ + protected TreePath editingPath; + + /** + * Row that is being edited. Should only be referenced if editingComponent is + * null. + */ + protected int editingRow; + + /** Set to true if the editor has a different size than the renderer. */ + protected boolean editorHasDifferentSize; + + /** Boolean to keep track of editing. */ + boolean isEditing; + + /** The current path of the visible nodes in the tree. */ + TreePath currentVisiblePath; + + /** The gap between the icon and text. */ + int gap = 4; + + /** The max height of the nodes in the tree. */ + int maxHeight; + + /** The hash color. */ + Color hashColor; + + /** Listeners */ + PropertyChangeListener propertyChangeListener; + + FocusListener focusListener; + + TreeSelectionListener treeSelectionListener; + + MouseListener mouseListener; + + KeyListener keyListener; + + PropertyChangeListener selectionModelPropertyChangeListener; + + ComponentListener componentListener; + + CellEditorListener cellEditorListener; + + TreeExpansionListener treeExpansionListener; + + TreeModelListener treeModelListener; + + /** + * The zero size icon, used for expand controls, if they are not visible. + */ + static Icon nullIcon; + + /** + * Creates a new BasicTreeUI object. + */ + public BasicTreeUI() + { + validCachedPreferredSize = false; + drawingCache = new Hashtable(); + nodeDimensions = createNodeDimensions(); + configureLayoutCache(); + + editingRow = - 1; + lastSelectedRow = - 1; + } + + /** + * Returns an instance of the UI delegate for the specified component. + * + * @param c the <code>JComponent</code> for which we need a UI delegate for. + * @return the <code>ComponentUI</code> for c. + */ + public static ComponentUI createUI(JComponent c) + { + return new BasicTreeUI(); + } + + /** + * Returns the Hash color. + * + * @return the <code>Color</code> of the Hash. + */ + protected Color getHashColor() + { + return hashColor; + } + + /** + * Sets the Hash color. + * + * @param color the <code>Color</code> to set the Hash to. + */ + protected void setHashColor(Color color) + { + hashColor = color; + } + + /** + * Sets the left child's indent value. + * + * @param newAmount is the new indent value for the left child. + */ + public void setLeftChildIndent(int newAmount) + { + leftChildIndent = newAmount; + } + + /** + * Returns the indent value for the left child. + * + * @return the indent value for the left child. + */ + public int getLeftChildIndent() + { + return leftChildIndent; + } + + /** + * Sets the right child's indent value. + * + * @param newAmount is the new indent value for the right child. + */ + public void setRightChildIndent(int newAmount) + { + rightChildIndent = newAmount; + } + + /** + * Returns the indent value for the right child. + * + * @return the indent value for the right child. + */ + public int getRightChildIndent() + { + return rightChildIndent; + } + + /** + * Sets the expanded icon. + * + * @param newG is the new expanded icon. + */ + public void setExpandedIcon(Icon newG) + { + expandedIcon = newG; + } + + /** + * Returns the current expanded icon. + * + * @return the current expanded icon. + */ + public Icon getExpandedIcon() + { + return expandedIcon; + } + + /** + * Sets the collapsed icon. + * + * @param newG is the new collapsed icon. + */ + public void setCollapsedIcon(Icon newG) + { + collapsedIcon = newG; + } + + /** + * Returns the current collapsed icon. + * + * @return the current collapsed icon. + */ + public Icon getCollapsedIcon() + { + return collapsedIcon; + } + + /** + * Updates the componentListener, if necessary. + * + * @param largeModel sets this.largeModel to it. + */ + protected void setLargeModel(boolean largeModel) + { + if (largeModel != this.largeModel) + { + completeEditing(); + tree.removeComponentListener(componentListener); + this.largeModel = largeModel; + tree.addComponentListener(componentListener); + } + } + + /** + * Returns true if largeModel is set + * + * @return true if largeModel is set, otherwise false. + */ + protected boolean isLargeModel() + { + return largeModel; + } + + /** + * Sets the row height. + * + * @param rowHeight is the height to set this.rowHeight to. + */ + protected void setRowHeight(int rowHeight) + { + completeEditing(); + if (rowHeight == 0) + rowHeight = getMaxHeight(tree); + treeState.setRowHeight(rowHeight); + } + + /** + * Returns the current row height. + * + * @return current row height. + */ + protected int getRowHeight() + { + return tree.getRowHeight(); + } + + /** + * Sets the TreeCellRenderer to <code>tcr</code>. This invokes + * <code>updateRenderer</code>. + * + * @param tcr is the new TreeCellRenderer. + */ + protected void setCellRenderer(TreeCellRenderer tcr) + { + // Finish editing before changing the renderer. + completeEditing(); + + // The renderer is set in updateRenderer. + updateRenderer(); + + // Refresh the layout if necessary. + if (treeState != null) + { + treeState.invalidateSizes(); + updateSize(); + } + } + + /** + * Return currentCellRenderer, which will either be the trees renderer, or + * defaultCellRenderer, which ever was not null. + * + * @return the current Cell Renderer + */ + protected TreeCellRenderer getCellRenderer() + { + if (currentCellRenderer != null) + return currentCellRenderer; + + return createDefaultCellRenderer(); + } + + /** + * Sets the tree's model. + * + * @param model to set the treeModel to. + */ + protected void setModel(TreeModel model) + { + completeEditing(); + + if (treeModel != null && treeModelListener != null) + treeModel.removeTreeModelListener(treeModelListener); + + treeModel = tree.getModel(); + + if (treeModel != null && treeModelListener != null) + treeModel.addTreeModelListener(treeModelListener); + + if (treeState != null) + { + treeState.setModel(treeModel); + updateLayoutCacheExpandedNodes(); + updateSize(); + } + } + + /** + * Returns the tree's model + * + * @return treeModel + */ + protected TreeModel getModel() + { + return treeModel; + } + + /** + * Sets the root to being visible. + * + * @param newValue sets the visibility of the root + */ + protected void setRootVisible(boolean newValue) + { + completeEditing(); + tree.setRootVisible(newValue); + } + + /** + * Returns true if the root is visible. + * + * @return true if the root is visible. + */ + protected boolean isRootVisible() + { + return tree.isRootVisible(); + } + + /** + * Determines whether the node handles are to be displayed. + * + * @param newValue sets whether or not node handles should be displayed. + */ + protected void setShowsRootHandles(boolean newValue) + { + completeEditing(); + updateDepthOffset(); + if (treeState != null) + { + treeState.invalidateSizes(); + updateSize(); + } + } + + /** + * Returns true if the node handles are to be displayed. + * + * @return true if the node handles are to be displayed. + */ + protected boolean getShowsRootHandles() + { + return tree.getShowsRootHandles(); + } + + /** + * Sets the cell editor. + * + * @param editor to set the cellEditor to. + */ + protected void setCellEditor(TreeCellEditor editor) + { + updateCellEditor(); + } + + /** + * Returns the <code>TreeCellEditor</code> for this tree. + * + * @return the cellEditor for this tree. + */ + protected TreeCellEditor getCellEditor() + { + return cellEditor; + } + + /** + * Configures the receiver to allow, or not allow, editing. + * + * @param newValue sets the receiver to allow editing if true. + */ + protected void setEditable(boolean newValue) + { + updateCellEditor(); + } + + /** + * Returns true if the receiver allows editing. + * + * @return true if the receiver allows editing. + */ + protected boolean isEditable() + { + return tree.isEditable(); + } + + /** + * Resets the selection model. The appropriate listeners are installed on the + * model. + * + * @param newLSM resets the selection model. + */ + protected void setSelectionModel(TreeSelectionModel newLSM) + { + completeEditing(); + if (newLSM != null) + { + treeSelectionModel = newLSM; + tree.setSelectionModel(treeSelectionModel); + } + } + + /** + * Returns the current selection model. + * + * @return the current selection model. + */ + protected TreeSelectionModel getSelectionModel() + { + return treeSelectionModel; + } + + /** + * Returns the Rectangle enclosing the label portion that the last item in + * path will be drawn to. Will return null if any component in path is + * currently valid. + * + * @param tree is the current tree the path will be drawn to. + * @param path is the current path the tree to draw to. + * @return the Rectangle enclosing the label portion that the last item in the + * path will be drawn to. + */ + public Rectangle getPathBounds(JTree tree, TreePath path) + { + Rectangle bounds = null; + if (tree != null && treeState != null) + { + bounds = treeState.getBounds(path, null); + Insets i = tree.getInsets(); + if (bounds != null && i != null) + { + bounds.x += i.left; + bounds.y += i.top; + } + } + return bounds; + } + + /** + * Returns the max height of all the nodes in the tree. + * + * @param tree - the current tree + * @return the max height. + */ + int getMaxHeight(JTree tree) + { + if (maxHeight != 0) + return maxHeight; + + Icon e = UIManager.getIcon("Tree.openIcon"); + Icon c = UIManager.getIcon("Tree.closedIcon"); + Icon l = UIManager.getIcon("Tree.leafIcon"); + int rc = getRowCount(tree); + int iconHeight = 0; + + for (int row = 0; row < rc; row++) + { + if (isLeaf(row)) + iconHeight = l.getIconHeight(); + else if (tree.isExpanded(row)) + iconHeight = e.getIconHeight(); + else + iconHeight = c.getIconHeight(); + + maxHeight = Math.max(maxHeight, iconHeight + gap); + } + + treeState.setRowHeight(maxHeight); + return maxHeight; + } + + /** + * Get the tree node icon. + */ + Icon getNodeIcon(TreePath path) + { + Object node = path.getLastPathComponent(); + if (treeModel.isLeaf(node)) + return UIManager.getIcon("Tree.leafIcon"); + else if (treeState.getExpandedState(path)) + return UIManager.getIcon("Tree.openIcon"); + else + return UIManager.getIcon("Tree.closedIcon"); + } + + /** + * Returns the path for passed in row. If row is not visible null is returned. + * + * @param tree is the current tree to return path for. + * @param row is the row number of the row to return. + * @return the path for passed in row. If row is not visible null is returned. + */ + public TreePath getPathForRow(JTree tree, int row) + { + return treeState.getPathForRow(row); + } + + /** + * Returns the row that the last item identified in path is visible at. Will + * return -1 if any of the elments in the path are not currently visible. + * + * @param tree is the current tree to return the row for. + * @param path is the path used to find the row. + * @return the row that the last item identified in path is visible at. Will + * return -1 if any of the elments in the path are not currently + * visible. + */ + public int getRowForPath(JTree tree, TreePath path) + { + return treeState.getRowForPath(path); + } + + /** + * Returns the number of rows that are being displayed. + * + * @param tree is the current tree to return the number of rows for. + * @return the number of rows being displayed. + */ + public int getRowCount(JTree tree) + { + return treeState.getRowCount(); + } + + /** + * Returns the path to the node that is closest to x,y. If there is nothing + * currently visible this will return null, otherwise it'll always return a + * valid path. If you need to test if the returned object is exactly at x,y + * you should get the bounds for the returned path and test x,y against that. + * + * @param tree the tree to search for the closest path + * @param x is the x coordinate of the location to search + * @param y is the y coordinate of the location to search + * @return the tree path closes to x,y. + */ + public TreePath getClosestPathForLocation(JTree tree, int x, int y) + { + return treeState.getPathClosestTo(x, y); + } + + /** + * Returns true if the tree is being edited. The item that is being edited can + * be returned by getEditingPath(). + * + * @param tree is the tree to check for editing. + * @return true if the tree is being edited. + */ + public boolean isEditing(JTree tree) + { + return isEditing; + } + + /** + * Stops the current editing session. This has no effect if the tree is not + * being edited. Returns true if the editor allows the editing session to + * stop. + * + * @param tree is the tree to stop the editing on + * @return true if the editor allows the editing session to stop. + */ + public boolean stopEditing(JTree tree) + { + boolean ret = false; + if (editingComponent != null && cellEditor.stopCellEditing()) + { + completeEditing(false, false, true); + ret = true; + } + return ret; + } + + /** + * Cancels the current editing session. + * + * @param tree is the tree to cancel the editing session on. + */ + public void cancelEditing(JTree tree) + { + // There is no need to send the cancel message to the editor, + // as the cancellation event itself arrives from it. This would + // only be necessary when cancelling the editing programatically. + if (editingComponent != null) + completeEditing(false, true, false); + } + + /** + * Selects the last item in path and tries to edit it. Editing will fail if + * the CellEditor won't allow it for the selected item. + * + * @param tree is the tree to edit on. + * @param path is the path in tree to edit on. + */ + public void startEditingAtPath(JTree tree, TreePath path) + { + tree.scrollPathToVisible(path); + if (path != null && tree.isVisible(path)) + startEditing(path, null); + } + + /** + * Returns the path to the element that is being editted. + * + * @param tree is the tree to get the editing path from. + * @return the path that is being edited. + */ + public TreePath getEditingPath(JTree tree) + { + return editingPath; + } + + /** + * Invoked after the tree instance variable has been set, but before any + * default/listeners have been installed. + */ + protected void prepareForUIInstall() + { + lastSelectedRow = -1; + preferredSize = new Dimension(); + largeModel = tree.isLargeModel(); + preferredSize = new Dimension(); + stopEditingInCompleteEditing = true; + setModel(tree.getModel()); + } + + /** + * Invoked from installUI after all the defaults/listeners have been + * installed. + */ + protected void completeUIInstall() + { + setShowsRootHandles(tree.getShowsRootHandles()); + updateRenderer(); + updateDepthOffset(); + setSelectionModel(tree.getSelectionModel()); + configureLayoutCache(); + treeState.setRootVisible(tree.isRootVisible()); + treeSelectionModel.setRowMapper(treeState); + updateSize(); + } + + /** + * Invoked from uninstallUI after all the defaults/listeners have been + * uninstalled. + */ + protected void completeUIUninstall() + { + tree = null; + } + + /** + * Installs the subcomponents of the tree, which is the renderer pane. + */ + protected void installComponents() + { + currentCellRenderer = createDefaultCellRenderer(); + rendererPane = createCellRendererPane(); + createdRenderer = true; + setCellRenderer(currentCellRenderer); + } + + /** + * Creates an instance of NodeDimensions that is able to determine the size of + * a given node in the tree. The node dimensions must be created before + * configuring the layout cache. + * + * @return the NodeDimensions of a given node in the tree + */ + protected AbstractLayoutCache.NodeDimensions createNodeDimensions() + { + return new NodeDimensionsHandler(); + } + + /** + * Creates a listener that is reponsible for the updates the UI based on how + * the tree changes. + * + * @return the PropertyChangeListener that is reposnsible for the updates + */ + protected PropertyChangeListener createPropertyChangeListener() + { + return new PropertyChangeHandler(); + } + + /** + * Creates the listener responsible for updating the selection based on mouse + * events. + * + * @return the MouseListener responsible for updating. + */ + protected MouseListener createMouseListener() + { + return new MouseHandler(); + } + + /** + * Creates the listener that is responsible for updating the display when + * focus is lost/grained. + * + * @return the FocusListener responsible for updating. + */ + protected FocusListener createFocusListener() + { + return new FocusHandler(); + } + + /** + * Creates the listener reponsible for getting key events from the tree. + * + * @return the KeyListener responsible for getting key events. + */ + protected KeyListener createKeyListener() + { + return new KeyHandler(); + } + + /** + * Creates the listener responsible for getting property change events from + * the selection model. + * + * @returns the PropertyChangeListener reponsible for getting property change + * events from the selection model. + */ + protected PropertyChangeListener createSelectionModelPropertyChangeListener() + { + return new SelectionModelPropertyChangeHandler(); + } + + /** + * Creates the listener that updates the display based on selection change + * methods. + * + * @return the TreeSelectionListener responsible for updating. + */ + protected TreeSelectionListener createTreeSelectionListener() + { + return new TreeSelectionHandler(); + } + + /** + * Creates a listener to handle events from the current editor + * + * @return the CellEditorListener that handles events from the current editor + */ + protected CellEditorListener createCellEditorListener() + { + return new CellEditorHandler(); + } + + /** + * Creates and returns a new ComponentHandler. This is used for the large + * model to mark the validCachedPreferredSize as invalid when the component + * moves. + * + * @return a new ComponentHandler. + */ + protected ComponentListener createComponentListener() + { + return new ComponentHandler(); + } + + /** + * Creates and returns the object responsible for updating the treestate when + * a nodes expanded state changes. + * + * @return the TreeExpansionListener responsible for updating the treestate + */ + protected TreeExpansionListener createTreeExpansionListener() + { + return new TreeExpansionHandler(); + } + + /** + * Creates the object responsible for managing what is expanded, as well as + * the size of nodes. + * + * @return the object responsible for managing what is expanded. + */ + protected AbstractLayoutCache createLayoutCache() + { + return new VariableHeightLayoutCache(); + } + + /** + * Returns the renderer pane that renderer components are placed in. + * + * @return the rendererpane that render components are placed in. + */ + protected CellRendererPane createCellRendererPane() + { + return new CellRendererPane(); + } + + /** + * Creates a default cell editor. + * + * @return the default cell editor. + */ + protected TreeCellEditor createDefaultCellEditor() + { + DefaultTreeCellEditor ed; + if (currentCellRenderer != null + && currentCellRenderer instanceof DefaultTreeCellRenderer) + ed = new DefaultTreeCellEditor(tree, + (DefaultTreeCellRenderer) currentCellRenderer); + else + ed = new DefaultTreeCellEditor(tree, null); + return ed; + } + + /** + * Returns the default cell renderer that is used to do the stamping of each + * node. + * + * @return the default cell renderer that is used to do the stamping of each + * node. + */ + protected TreeCellRenderer createDefaultCellRenderer() + { + return new DefaultTreeCellRenderer(); + } + + /** + * Returns a listener that can update the tree when the model changes. + * + * @return a listener that can update the tree when the model changes. + */ + protected TreeModelListener createTreeModelListener() + { + return new TreeModelHandler(); + } + + /** + * Uninstall all registered listeners + */ + protected void uninstallListeners() + { + tree.removePropertyChangeListener(propertyChangeListener); + tree.removeFocusListener(focusListener); + tree.removeTreeSelectionListener(treeSelectionListener); + tree.removeMouseListener(mouseListener); + tree.removeKeyListener(keyListener); + tree.removePropertyChangeListener(selectionModelPropertyChangeListener); + tree.removeComponentListener(componentListener); + tree.removeTreeExpansionListener(treeExpansionListener); + + TreeCellEditor tce = tree.getCellEditor(); + if (tce != null) + tce.removeCellEditorListener(cellEditorListener); + if (treeModel != null) + treeModel.removeTreeModelListener(treeModelListener); + } + + /** + * Uninstall all keyboard actions. + */ + protected void uninstallKeyboardActions() + { + tree.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).setParent( + null); + tree.getActionMap().setParent(null); + } + + /** + * Uninstall the rendererPane. + */ + protected void uninstallComponents() + { + currentCellRenderer = null; + rendererPane = null; + createdRenderer = false; + setCellRenderer(currentCellRenderer); + } + + /** + * The vertical element of legs between nodes starts at the bottom of the + * parent node by default. This method makes the leg start below that. + * + * @return the vertical leg buffer + */ + protected int getVerticalLegBuffer() + { + return getRowHeight() / 2; + } + + /** + * The horizontal element of legs between nodes starts at the right of the + * left-hand side of the child node by default. This method makes the leg end + * before that. + * + * @return the horizontal leg buffer + */ + protected int getHorizontalLegBuffer() + { + return rightChildIndent / 2; + } + + /** + * Make all the nodes that are expanded in JTree expanded in LayoutCache. This + * invokes updateExpandedDescendants with the root path. + */ + protected void updateLayoutCacheExpandedNodes() + { + if (treeModel != null && treeModel.getRoot() != null) + updateExpandedDescendants(new TreePath(treeModel.getRoot())); + } + + /** + * Updates the expanded state of all the descendants of the <code>path</code> + * by getting the expanded descendants from the tree and forwarding to the + * tree state. + * + * @param path the path used to update the expanded states + */ + protected void updateExpandedDescendants(TreePath path) + { + completeEditing(); + Enumeration expanded = tree.getExpandedDescendants(path); + while (expanded.hasMoreElements()) + treeState.setExpandedState((TreePath) expanded.nextElement(), true); + } + + /** + * Returns a path to the last child of <code>parent</code> + * + * @param parent is the topmost path to specified + * @return a path to the last child of parent + */ + protected TreePath getLastChildPath(TreePath parent) + { + return (TreePath) parent.getLastPathComponent(); + } + + /** + * Updates how much each depth should be offset by. + */ + protected void updateDepthOffset() + { + depthOffset += getVerticalLegBuffer(); + } + + /** + * Updates the cellEditor based on editability of the JTree that we're + * contained in. If the tree is editable but doesn't have a cellEditor, a + * basic one will be used. + */ + protected void updateCellEditor() + { + completeEditing(); + TreeCellEditor newEd = null; + if (tree != null && tree.isEditable()) + { + newEd = tree.getCellEditor(); + if (newEd == null) + { + newEd = createDefaultCellEditor(); + if (newEd != null) + { + tree.setCellEditor(newEd); + createdCellEditor = true; + } + } + } + // Update listeners. + if (newEd != cellEditor) + { + if (cellEditor != null && cellEditorListener != null) + cellEditor.removeCellEditorListener(cellEditorListener); + cellEditor = newEd; + if (cellEditorListener == null) + cellEditorListener = createCellEditorListener(); + if (cellEditor != null && cellEditorListener != null) + cellEditor.addCellEditorListener(cellEditorListener); + createdCellEditor = false; + } + } + + /** + * Messaged from the tree we're in when the renderer has changed. + */ + protected void updateRenderer() + { + if (tree != null) + { + TreeCellRenderer rend = tree.getCellRenderer(); + if (rend != null) + { + createdRenderer = false; + currentCellRenderer = rend; + if (createdCellEditor) + tree.setCellEditor(null); + } + else + { + tree.setCellRenderer(createDefaultCellRenderer()); + createdRenderer = true; + } + } + else + { + currentCellRenderer = null; + createdRenderer = false; + } + + updateCellEditor(); + } + + /** + * Resets the treeState instance based on the tree we're providing the look + * and feel for. The node dimensions handler is required and must be created + * in advance. + */ + protected void configureLayoutCache() + { + treeState = createLayoutCache(); + treeState.setNodeDimensions(nodeDimensions); + } + + /** + * Marks the cached size as being invalid, and messages the tree with + * <code>treeDidChange</code>. + */ + protected void updateSize() + { + preferredSize = null; + updateCachedPreferredSize(); + tree.treeDidChange(); + } + + /** + * Updates the <code>preferredSize</code> instance variable, which is + * returned from <code>getPreferredSize()</code>. + */ + protected void updateCachedPreferredSize() + { + validCachedPreferredSize = false; + } + + /** + * Messaged from the VisibleTreeNode after it has been expanded. + * + * @param path is the path that has been expanded. + */ + protected void pathWasExpanded(TreePath path) + { + validCachedPreferredSize = false; + treeState.setExpandedState(path, true); + tree.repaint(); + } + + /** + * Messaged from the VisibleTreeNode after it has collapsed + */ + protected void pathWasCollapsed(TreePath path) + { + validCachedPreferredSize = false; + treeState.setExpandedState(path, false); + tree.repaint(); + } + + /** + * Install all defaults for the tree. + */ + protected void installDefaults() + { + LookAndFeel.installColorsAndFont(tree, "Tree.background", + "Tree.foreground", "Tree.font"); + + hashColor = UIManager.getColor("Tree.hash"); + if (hashColor == null) + hashColor = Color.black; + + tree.setOpaque(true); + + rightChildIndent = UIManager.getInt("Tree.rightChildIndent"); + leftChildIndent = UIManager.getInt("Tree.leftChildIndent"); + totalChildIndent = rightChildIndent + leftChildIndent; + setRowHeight(UIManager.getInt("Tree.rowHeight")); + tree.setRowHeight(getRowHeight()); + tree.setScrollsOnExpand(UIManager.getBoolean("Tree.scrollsOnExpand")); + setExpandedIcon(UIManager.getIcon("Tree.expandedIcon")); + setCollapsedIcon(UIManager.getIcon("Tree.collapsedIcon")); + } + + /** + * Install all keyboard actions for this + */ + protected void installKeyboardActions() + { + InputMap focusInputMap = + (InputMap) SharedUIDefaults.get("Tree.focusInputMap"); + SwingUtilities.replaceUIInputMap(tree, JComponent.WHEN_FOCUSED, + focusInputMap); + InputMap ancestorInputMap = + (InputMap) SharedUIDefaults.get("Tree.ancestorInputMap"); + SwingUtilities.replaceUIInputMap(tree, + JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, + ancestorInputMap); + + SwingUtilities.replaceUIActionMap(tree, getActionMap()); + } + + /** + * Creates and returns the shared action map for JTrees. + * + * @return the shared action map for JTrees + */ + private ActionMap getActionMap() + { + ActionMap am = (ActionMap) UIManager.get("Tree.actionMap"); + if (am == null) + { + am = createDefaultActions(); + UIManager.getLookAndFeelDefaults().put("Tree.actionMap", am); + } + return am; + } + + /** + * Creates the default actions when there are none specified by the L&F. + * + * @return the default actions + */ + private ActionMap createDefaultActions() + { + ActionMapUIResource am = new ActionMapUIResource(); + Action action; + + // TreeHomeAction. + action = new TreeHomeAction(-1, "selectFirst"); + am.put(action.getValue(Action.NAME), action); + action = new TreeHomeAction(-1, "selectFirstChangeLead"); + am.put(action.getValue(Action.NAME), action); + action = new TreeHomeAction(-1, "selectFirstExtendSelection"); + am.put(action.getValue(Action.NAME), action); + action = new TreeHomeAction(1, "selectLast"); + am.put(action.getValue(Action.NAME), action); + action = new TreeHomeAction(1, "selectLastChangeLead"); + am.put(action.getValue(Action.NAME), action); + action = new TreeHomeAction(1, "selectLastExtendSelection"); + am.put(action.getValue(Action.NAME), action); + + // TreeIncrementAction. + action = new TreeIncrementAction(-1, "selectPrevious"); + am.put(action.getValue(Action.NAME), action); + action = new TreeIncrementAction(-1, "selectPreviousExtendSelection"); + am.put(action.getValue(Action.NAME), action); + action = new TreeIncrementAction(-1, "selectPreviousChangeLead"); + am.put(action.getValue(Action.NAME), action); + action = new TreeIncrementAction(1, "selectNext"); + am.put(action.getValue(Action.NAME), action); + action = new TreeIncrementAction(1, "selectNextExtendSelection"); + am.put(action.getValue(Action.NAME), action); + action = new TreeIncrementAction(1, "selectNextChangeLead"); + am.put(action.getValue(Action.NAME), action); + + // TreeTraverseAction. + action = new TreeTraverseAction(-1, "selectParent"); + am.put(action.getValue(Action.NAME), action); + action = new TreeTraverseAction(1, "selectChild"); + am.put(action.getValue(Action.NAME), action); + + // TreeToggleAction. + action = new TreeToggleAction("toggleAndAnchor"); + am.put(action.getValue(Action.NAME), action); + + // TreePageAction. + action = new TreePageAction(-1, "scrollUpChangeSelection"); + am.put(action.getValue(Action.NAME), action); + action = new TreePageAction(-1, "scrollUpExtendSelection"); + am.put(action.getValue(Action.NAME), action); + action = new TreePageAction(-1, "scrollUpChangeLead"); + am.put(action.getValue(Action.NAME), action); + action = new TreePageAction(1, "scrollDownChangeSelection"); + am.put(action.getValue(Action.NAME), action); + action = new TreePageAction(1, "scrollDownExtendSelection"); + am.put(action.getValue(Action.NAME), action); + action = new TreePageAction(1, "scrollDownChangeLead"); + am.put(action.getValue(Action.NAME), action); + + // Tree editing actions + action = new TreeStartEditingAction("startEditing"); + am.put(action.getValue(Action.NAME), action); + action = new TreeCancelEditingAction("cancel"); + am.put(action.getValue(Action.NAME), action); + + + return am; + } + + /** + * Converts the modifiers. + * + * @param mod - modifier to convert + * @returns the new modifier + */ + private int convertModifiers(int mod) + { + if ((mod & KeyEvent.SHIFT_DOWN_MASK) != 0) + { + mod |= KeyEvent.SHIFT_MASK; + mod &= ~ KeyEvent.SHIFT_DOWN_MASK; + } + if ((mod & KeyEvent.CTRL_DOWN_MASK) != 0) + { + mod |= KeyEvent.CTRL_MASK; + mod &= ~ KeyEvent.CTRL_DOWN_MASK; + } + if ((mod & KeyEvent.META_DOWN_MASK) != 0) + { + mod |= KeyEvent.META_MASK; + mod &= ~ KeyEvent.META_DOWN_MASK; + } + if ((mod & KeyEvent.ALT_DOWN_MASK) != 0) + { + mod |= KeyEvent.ALT_MASK; + mod &= ~ KeyEvent.ALT_DOWN_MASK; + } + if ((mod & KeyEvent.ALT_GRAPH_DOWN_MASK) != 0) + { + mod |= KeyEvent.ALT_GRAPH_MASK; + mod &= ~ KeyEvent.ALT_GRAPH_DOWN_MASK; + } + return mod; + } + + /** + * Install all listeners for this + */ + protected void installListeners() + { + propertyChangeListener = createPropertyChangeListener(); + tree.addPropertyChangeListener(propertyChangeListener); + + focusListener = createFocusListener(); + tree.addFocusListener(focusListener); + + treeSelectionListener = createTreeSelectionListener(); + tree.addTreeSelectionListener(treeSelectionListener); + + mouseListener = createMouseListener(); + tree.addMouseListener(mouseListener); + + keyListener = createKeyListener(); + tree.addKeyListener(keyListener); + + selectionModelPropertyChangeListener = + createSelectionModelPropertyChangeListener(); + if (treeSelectionModel != null + && selectionModelPropertyChangeListener != null) + { + treeSelectionModel.addPropertyChangeListener( + selectionModelPropertyChangeListener); + } + + componentListener = createComponentListener(); + tree.addComponentListener(componentListener); + + treeExpansionListener = createTreeExpansionListener(); + tree.addTreeExpansionListener(treeExpansionListener); + + treeModelListener = createTreeModelListener(); + if (treeModel != null) + treeModel.addTreeModelListener(treeModelListener); + + cellEditorListener = createCellEditorListener(); + } + + /** + * Install the UI for the component + * + * @param c the component to install UI for + */ + public void installUI(JComponent c) + { + tree = (JTree) c; + + prepareForUIInstall(); + installDefaults(); + installComponents(); + installKeyboardActions(); + installListeners(); + completeUIInstall(); + } + + /** + * Uninstall the defaults for the tree + */ + protected void uninstallDefaults() + { + tree.setFont(null); + tree.setForeground(null); + tree.setBackground(null); + } + + /** + * Uninstall the UI for the component + * + * @param c the component to uninstall UI for + */ + public void uninstallUI(JComponent c) + { + completeEditing(); + + prepareForUIUninstall(); + uninstallDefaults(); + uninstallKeyboardActions(); + uninstallListeners(); + uninstallComponents(); + completeUIUninstall(); + } + + /** + * Paints the specified component appropriate for the look and feel. This + * method is invoked from the ComponentUI.update method when the specified + * component is being painted. Subclasses should override this method and use + * the specified Graphics object to render the content of the component. + * + * @param g the Graphics context in which to paint + * @param c the component being painted; this argument is often ignored, but + * might be used if the UI object is stateless and shared by multiple + * components + */ + public void paint(Graphics g, JComponent c) + { + JTree tree = (JTree) c; + + int rows = treeState.getRowCount(); + + if (rows == 0) + // There is nothing to do if the tree is empty. + return; + + Rectangle clip = g.getClipBounds(); + + Insets insets = tree.getInsets(); + + if (clip != null && treeModel != null) + { + int startIndex = tree.getClosestRowForLocation(clip.x, clip.y); + int endIndex = tree.getClosestRowForLocation(clip.x + clip.width, + clip.y + clip.height); + // Also paint dashes to the invisible nodes below. + // These should be painted first, otherwise they may cover + // the control icons. + if (endIndex < rows) + for (int i = endIndex + 1; i < rows; i++) + { + TreePath path = treeState.getPathForRow(i); + if (isLastChild(path)) + paintVerticalPartOfLeg(g, clip, insets, path); + } + + // The two loops are required to ensure that the lines are not + // painted over the other tree components. + + int n = endIndex - startIndex + 1; + Rectangle[] bounds = new Rectangle[n]; + boolean[] isLeaf = new boolean[n]; + boolean[] isExpanded = new boolean[n]; + TreePath[] path = new TreePath[n]; + int k; + + k = 0; + for (int i = startIndex; i <= endIndex; i++, k++) + { + path[k] = treeState.getPathForRow(i); + if (path[k] != null) + { + isLeaf[k] = treeModel.isLeaf(path[k].getLastPathComponent()); + isExpanded[k] = tree.isExpanded(path[k]); + bounds[k] = getPathBounds(tree, path[k]); + + paintHorizontalPartOfLeg(g, clip, insets, bounds[k], path[k], + i, isExpanded[k], false, isLeaf[k]); + } + if (isLastChild(path[k])) + paintVerticalPartOfLeg(g, clip, insets, path[k]); + } + + k = 0; + for (int i = startIndex; i <= endIndex; i++, k++) + { + if (path[k] != null) + paintRow(g, clip, insets, bounds[k], path[k], i, isExpanded[k], + false, isLeaf[k]); + } + } + } + + /** + * Check if the path is referring to the last child of some parent. + */ + private boolean isLastChild(TreePath path) + { + if (path == null) + return false; + else if (path instanceof GnuPath) + { + // Except the seldom case when the layout cache is changed, this + // optimized code will be executed. + return ((GnuPath) path).isLastChild; + } + else + { + // Non optimized general case. + TreePath parent = path.getParentPath(); + if (parent == null) + return false; + int childCount = treeState.getVisibleChildCount(parent); + int p = treeModel.getIndexOfChild(parent, path.getLastPathComponent()); + return p == childCount - 1; + } + } + + /** + * Ensures that the rows identified by beginRow through endRow are visible. + * + * @param beginRow is the first row + * @param endRow is the last row + */ + protected void ensureRowsAreVisible(int beginRow, int endRow) + { + if (beginRow < endRow) + { + int temp = endRow; + endRow = beginRow; + beginRow = temp; + } + + for (int i = beginRow; i < endRow; i++) + { + TreePath path = getPathForRow(tree, i); + if (! tree.isVisible(path)) + tree.makeVisible(path); + } + } + + /** + * Sets the preferred minimum size. + * + * @param newSize is the new preferred minimum size. + */ + public void setPreferredMinSize(Dimension newSize) + { + preferredMinSize = newSize; + } + + /** + * Gets the preferred minimum size. + * + * @returns the preferred minimum size. + */ + public Dimension getPreferredMinSize() + { + if (preferredMinSize == null) + return getPreferredSize(tree); + else + return preferredMinSize; + } + + /** + * Returns the preferred size to properly display the tree, this is a cover + * method for getPreferredSize(c, false). + * + * @param c the component whose preferred size is being queried; this argument + * is often ignored but might be used if the UI object is stateless + * and shared by multiple components + * @return the preferred size + */ + public Dimension getPreferredSize(JComponent c) + { + return getPreferredSize(c, false); + } + + /** + * Returns the preferred size to represent the tree in c. If checkConsistancy + * is true, checkConsistancy is messaged first. + * + * @param c the component whose preferred size is being queried. + * @param checkConsistancy if true must check consistancy + * @return the preferred size + */ + public Dimension getPreferredSize(JComponent c, boolean checkConsistancy) + { + if (! validCachedPreferredSize) + { + Rectangle size = tree.getBounds(); + // Add the scrollbar dimensions to the preferred size. + preferredSize = new Dimension(treeState.getPreferredWidth(size), + treeState.getPreferredHeight()); + validCachedPreferredSize = true; + } + return preferredSize; + } + + /** + * Returns the minimum size for this component. Which will be the min + * preferred size or (0,0). + * + * @param c the component whose min size is being queried. + * @returns the preferred size or null + */ + public Dimension getMinimumSize(JComponent c) + { + return preferredMinSize = getPreferredSize(c); + } + + /** + * Returns the maximum size for the component, which will be the preferred + * size if the instance is currently in JTree or (0,0). + * + * @param c the component whose preferred size is being queried + * @return the max size or null + */ + public Dimension getMaximumSize(JComponent c) + { + return getPreferredSize(c); + } + + /** + * Messages to stop the editing session. If the UI the receiver is providing + * the look and feel for returns true from + * <code>getInvokesStopCellEditing</code>, stopCellEditing will be invoked + * on the current editor. Then completeEditing will be messaged with false, + * true, false to cancel any lingering editing. + */ + protected void completeEditing() + { + if (tree.getInvokesStopCellEditing() && stopEditingInCompleteEditing + && editingComponent != null) + cellEditor.stopCellEditing(); + + completeEditing(false, true, false); + } + + /** + * Stops the editing session. If messageStop is true, the editor is messaged + * with stopEditing, if messageCancel is true the editor is messaged with + * cancelEditing. If messageTree is true, the treeModel is messaged with + * valueForPathChanged. + * + * @param messageStop message to stop editing + * @param messageCancel message to cancel editing + * @param messageTree message to treeModel + */ + protected void completeEditing(boolean messageStop, boolean messageCancel, + boolean messageTree) + { + // Make no attempt to complete the non existing editing session. + if (stopEditingInCompleteEditing && editingComponent != null) + { + Component comp = editingComponent; + TreePath p = editingPath; + editingComponent = null; + editingPath = null; + if (messageStop) + cellEditor.stopCellEditing(); + else if (messageCancel) + cellEditor.cancelCellEditing(); + + tree.remove(comp); + + if (editorHasDifferentSize) + { + treeState.invalidatePathBounds(p); + updateSize(); + } + else + { + // Need to refresh the tree. + Rectangle b = getPathBounds(tree, p); + tree.repaint(0, b.y, tree.getWidth(), b.height); + } + + if (messageTree) + { + Object value = cellEditor.getCellEditorValue(); + treeModel.valueForPathChanged(p, value); + } + } + } + + /** + * Will start editing for node if there is a cellEditor and shouldSelectCall + * returns true. This assumes that path is valid and visible. + * + * @param path is the path to start editing + * @param event is the MouseEvent performed on the path + * @return true if successful + */ + protected boolean startEditing(TreePath path, MouseEvent event) + { + // Maybe cancel editing. + if (isEditing(tree) && tree.getInvokesStopCellEditing() + && ! stopEditing(tree)) + return false; + + completeEditing(); + TreeCellEditor ed = cellEditor; + if (ed != null && tree.isPathEditable(path)) + { + if (ed.isCellEditable(event)) + { + editingRow = getRowForPath(tree, path); + Object value = path.getLastPathComponent(); + boolean isSelected = tree.isPathSelected(path); + boolean isExpanded = tree.isExpanded(editingPath); + boolean isLeaf = treeModel.isLeaf(value); + editingComponent = ed.getTreeCellEditorComponent(tree, value, + isSelected, + isExpanded, + isLeaf, + editingRow); + + Rectangle bounds = getPathBounds(tree, path); + + Dimension size = editingComponent.getPreferredSize(); + int rowHeight = getRowHeight(); + if (size.height != bounds.height && rowHeight > 0) + size.height = rowHeight; + + if (size.width != bounds.width || size.height != bounds.height) + { + editorHasDifferentSize = true; + treeState.invalidatePathBounds(path); + updateSize(); + } + else + editorHasDifferentSize = false; + + // The editing component must be added to its container. We add the + // container, not the editing component itself. + tree.add(editingComponent); + editingComponent.setBounds(bounds.x, bounds.y, size.width, + size.height); + editingComponent.validate(); + editingPath = path; + + if (ed.shouldSelectCell(event)) + { + stopEditingInCompleteEditing = false; + tree.setSelectionRow(editingRow); + stopEditingInCompleteEditing = true; + } + + editorRequestFocus(editingComponent); + // Register MouseInputHandler to redispatch initial mouse events + // correctly. + if (event instanceof MouseEvent) + { + Point p = SwingUtilities.convertPoint(tree, event.getX(), event.getY(), + editingComponent); + Component active = + SwingUtilities.getDeepestComponentAt(editingComponent, p.x, p.y); + if (active != null) + { + MouseInputHandler ih = new MouseInputHandler(tree, active, event); + + } + } + + return true; + } + else + editingComponent = null; + } + return false; + } + + /** + * Requests focus on the editor. The method is necessary since the + * DefaultTreeCellEditor returns a container that contains the + * actual editor, and we want to request focus on the editor, not the + * container. + */ + private void editorRequestFocus(Component c) + { + if (c instanceof Container) + { + // TODO: Maybe do something more reasonable here, like queriying the + // FocusTraversalPolicy. + Container cont = (Container) c; + if (cont.getComponentCount() > 0) + cont.getComponent(0).requestFocus(); + } + else if (c.isFocusable()) + c.requestFocus(); + + } + + /** + * If the <code>mouseX</code> and <code>mouseY</code> are in the expand or + * collapse region of the row, this will toggle the row. + * + * @param path the path we are concerned with + * @param mouseX is the cursor's x position + * @param mouseY is the cursor's y position + */ + protected void checkForClickInExpandControl(TreePath path, int mouseX, + int mouseY) + { + if (isLocationInExpandControl(path, mouseX, mouseY)) + handleExpandControlClick(path, mouseX, mouseY); + } + + /** + * Returns true if the <code>mouseX</code> and <code>mouseY</code> fall in + * the area of row that is used to expand/collpse the node and the node at row + * does not represent a leaf. + * + * @param path the path we are concerned with + * @param mouseX is the cursor's x position + * @param mouseY is the cursor's y position + * @return true if the <code>mouseX</code> and <code>mouseY</code> fall in + * the area of row that is used to expand/collpse the node and the + * node at row does not represent a leaf. + */ + protected boolean isLocationInExpandControl(TreePath path, int mouseX, + int mouseY) + { + boolean cntlClick = false; + if (! treeModel.isLeaf(path.getLastPathComponent())) + { + int width; + Icon expandedIcon = getExpandedIcon(); + if (expandedIcon != null) + width = expandedIcon.getIconWidth(); + else + // Only guessing. This is the width of + // the tree control icon in Metal L&F. + width = 18; + + Insets i = tree.getInsets(); + + int depth; + if (isRootVisible()) + depth = path.getPathCount()-1; + else + depth = path.getPathCount()-2; + + int left = getRowX(tree.getRowForPath(path), depth) + - width + i.left; + cntlClick = mouseX >= left && mouseX <= left + width; + } + return cntlClick; + } + + /** + * Messaged when the user clicks the particular row, this invokes + * toggleExpandState. + * + * @param path the path we are concerned with + * @param mouseX is the cursor's x position + * @param mouseY is the cursor's y position + */ + protected void handleExpandControlClick(TreePath path, int mouseX, int mouseY) + { + toggleExpandState(path); + } + + /** + * Expands path if it is not expanded, or collapses row if it is expanded. If + * expanding a path and JTree scroll on expand, ensureRowsAreVisible is + * invoked to scroll as many of the children to visible as possible (tries to + * scroll to last visible descendant of path). + * + * @param path the path we are concerned with + */ + protected void toggleExpandState(TreePath path) + { + // tree.isExpanded(path) would do the same, but treeState knows faster. + if (treeState.isExpanded(path)) + tree.collapsePath(path); + else + tree.expandPath(path); + } + + /** + * Returning true signifies a mouse event on the node should toggle the + * selection of only the row under the mouse. The BasisTreeUI treats the + * event as "toggle selection event" if the CTRL button was pressed while + * clicking. The event is not counted as toggle event if the associated + * tree does not support the multiple selection. + * + * @param event is the MouseEvent performed on the row. + * @return true signifies a mouse event on the node should toggle the + * selection of only the row under the mouse. + */ + protected boolean isToggleSelectionEvent(MouseEvent event) + { + return + (tree.getSelectionModel().getSelectionMode() != + TreeSelectionModel.SINGLE_TREE_SELECTION) && + ((event.getModifiersEx() & InputEvent.CTRL_DOWN_MASK) != 0); + } + + /** + * Returning true signifies a mouse event on the node should select from the + * anchor point. The BasisTreeUI treats the event as "multiple selection + * event" if the SHIFT button was pressed while clicking. The event is not + * counted as multiple selection event if the associated tree does not support + * the multiple selection. + * + * @param event is the MouseEvent performed on the node. + * @return true signifies a mouse event on the node should select from the + * anchor point. + */ + protected boolean isMultiSelectEvent(MouseEvent event) + { + return + (tree.getSelectionModel().getSelectionMode() != + TreeSelectionModel.SINGLE_TREE_SELECTION) && + ((event.getModifiersEx() & InputEvent.SHIFT_DOWN_MASK) != 0); + } + + /** + * Returning true indicates the row under the mouse should be toggled based on + * the event. This is invoked after checkForClickInExpandControl, implying the + * location is not in the expand (toggle) control. + * + * @param event is the MouseEvent performed on the row. + * @return true indicates the row under the mouse should be toggled based on + * the event. + */ + protected boolean isToggleEvent(MouseEvent event) + { + boolean toggle = false; + if (SwingUtilities.isLeftMouseButton(event)) + { + int clickCount = tree.getToggleClickCount(); + if (clickCount > 0 && event.getClickCount() == clickCount) + toggle = true; + } + return toggle; + } + + /** + * Messaged to update the selection based on a MouseEvent over a particular + * row. If the even is a toggle selection event, the row is either selected, + * or deselected. If the event identifies a multi selection event, the + * selection is updated from the anchor point. Otherwise, the row is selected, + * and the previous selection is cleared.</p> + * + * @param path is the path selected for an event + * @param event is the MouseEvent performed on the path. + * + * @see #isToggleSelectionEvent(MouseEvent) + * @see #isMultiSelectEvent(MouseEvent) + */ + protected void selectPathForEvent(TreePath path, MouseEvent event) + { + if (isToggleSelectionEvent(event)) + { + // The event selects or unselects the clicked row. + if (tree.isPathSelected(path)) + tree.removeSelectionPath(path); + else + { + tree.addSelectionPath(path); + tree.setAnchorSelectionPath(path); + } + } + else if (isMultiSelectEvent(event)) + { + // The event extends selection form anchor till the clicked row. + TreePath anchor = tree.getAnchorSelectionPath(); + if (anchor != null) + { + int aRow = getRowForPath(tree, anchor); + tree.addSelectionInterval(aRow, getRowForPath(tree, path)); + } + else + tree.addSelectionPath(path); + } + else + { + // This is an ordinary event that just selects the clicked row. + tree.setSelectionPath(path); + if (isToggleEvent(event)) + toggleExpandState(path); + } + } + + /** + * Returns true if the node at <code>row</code> is a leaf. + * + * @param row is the row we are concerned with. + * @return true if the node at <code>row</code> is a leaf. + */ + protected boolean isLeaf(int row) + { + TreePath pathForRow = getPathForRow(tree, row); + if (pathForRow == null) + return true; + + Object node = pathForRow.getLastPathComponent(); + return treeModel.isLeaf(node); + } + + /** + * The action to start editing at the current lead selection path. + */ + class TreeStartEditingAction + extends AbstractAction + { + /** + * Creates the new tree cancel editing action. + * + * @param name the name of the action (used in toString). + */ + public TreeStartEditingAction(String name) + { + super(name); + } + + /** + * Start editing at the current lead selection path. + * + * @param e the ActionEvent that caused this action. + */ + public void actionPerformed(ActionEvent e) + { + TreePath lead = tree.getLeadSelectionPath(); + if (!tree.isEditing()) + tree.startEditingAtPath(lead); + } + } + + /** + * Updates the preferred size when scrolling, if necessary. + */ + public class ComponentHandler + extends ComponentAdapter + implements ActionListener + { + /** + * Timer used when inside a scrollpane and the scrollbar is adjusting + */ + protected Timer timer; + + /** ScrollBar that is being adjusted */ + protected JScrollBar scrollBar; + + /** + * Constructor + */ + public ComponentHandler() + { + // Nothing to do here. + } + + /** + * Invoked when the component's position changes. + * + * @param e the event that occurs when moving the component + */ + public void componentMoved(ComponentEvent e) + { + if (timer == null) + { + JScrollPane scrollPane = getScrollPane(); + if (scrollPane == null) + updateSize(); + else + { + // Determine the scrollbar that is adjusting, if any, and + // start the timer for that. If no scrollbar is adjusting, + // we simply call updateSize(). + scrollBar = scrollPane.getVerticalScrollBar(); + if (scrollBar == null || !scrollBar.getValueIsAdjusting()) + { + // It's not the vertical scrollbar, try the horizontal one. + scrollBar = scrollPane.getHorizontalScrollBar(); + if (scrollBar != null && scrollBar.getValueIsAdjusting()) + startTimer(); + else + updateSize(); + } + else + { + startTimer(); + } + } + } + } + + /** + * Creates, if necessary, and starts a Timer to check if needed to resize + * the bounds + */ + protected void startTimer() + { + if (timer == null) + { + timer = new Timer(200, this); + timer.setRepeats(true); + } + timer.start(); + } + + /** + * Returns the JScrollPane housing the JTree, or null if one isn't found. + * + * @return JScrollPane housing the JTree, or null if one isn't found. + */ + protected JScrollPane getScrollPane() + { + JScrollPane found = null; + Component p = tree.getParent(); + while (p != null && !(p instanceof JScrollPane)) + p = p.getParent(); + if (p instanceof JScrollPane) + found = (JScrollPane) p; + return found; + } + + /** + * Public as a result of Timer. If the scrollBar is null, or not adjusting, + * this stops the timer and updates the sizing. + * + * @param ae is the action performed + */ + public void actionPerformed(ActionEvent ae) + { + if (scrollBar == null || !scrollBar.getValueIsAdjusting()) + { + if (timer != null) + timer.stop(); + updateSize(); + timer = null; + scrollBar = null; + } + } + } + + /** + * Listener responsible for getting cell editing events and updating the tree + * accordingly. + */ + public class CellEditorHandler + implements CellEditorListener + { + /** + * Constructor + */ + public CellEditorHandler() + { + // Nothing to do here. + } + + /** + * Messaged when editing has stopped in the tree. Tells the listeners + * editing has stopped. + * + * @param e is the notification event + */ + public void editingStopped(ChangeEvent e) + { + completeEditing(false, false, true); + } + + /** + * Messaged when editing has been canceled in the tree. This tells the + * listeners the editor has canceled editing. + * + * @param e is the notification event + */ + public void editingCanceled(ChangeEvent e) + { + completeEditing(false, false, false); + } + } // CellEditorHandler + + /** + * Repaints the lead selection row when focus is lost/grained. + */ + public class FocusHandler + implements FocusListener + { + /** + * Constructor + */ + public FocusHandler() + { + // Nothing to do here. + } + + /** + * Invoked when focus is activated on the tree we're in, redraws the lead + * row. Invoked when a component gains the keyboard focus. The method + * repaints the lead row that is shown differently when the tree is in + * focus. + * + * @param e is the focus event that is activated + */ + public void focusGained(FocusEvent e) + { + repaintLeadRow(); + } + + /** + * Invoked when focus is deactivated on the tree we're in, redraws the lead + * row. Invoked when a component loses the keyboard focus. The method + * repaints the lead row that is shown differently when the tree is in + * focus. + * + * @param e is the focus event that is deactivated + */ + public void focusLost(FocusEvent e) + { + repaintLeadRow(); + } + + /** + * Repaint the lead row. + */ + void repaintLeadRow() + { + TreePath lead = tree.getLeadSelectionPath(); + if (lead != null) + tree.repaint(tree.getPathBounds(lead)); + } + } + + /** + * This is used to get multiple key down events to appropriately genereate + * events. + */ + public class KeyHandler + extends KeyAdapter + { + /** Key code that is being generated for. */ + protected Action repeatKeyAction; + + /** Set to true while keyPressed is active */ + protected boolean isKeyDown; + + /** + * Constructor + */ + public KeyHandler() + { + // Nothing to do here. + } + + /** + * Invoked when a key has been typed. Moves the keyboard focus to the first + * element whose first letter matches the alphanumeric key pressed by the + * user. Subsequent same key presses move the keyboard focus to the next + * object that starts with the same letter. + * + * @param e the key typed + */ + public void keyTyped(KeyEvent e) + { + char typed = Character.toLowerCase(e.getKeyChar()); + for (int row = tree.getLeadSelectionRow() + 1; + row < tree.getRowCount(); row++) + { + if (checkMatch(row, typed)) + { + tree.setSelectionRow(row); + tree.scrollRowToVisible(row); + return; + } + } + + // Not found below, search above: + for (int row = 0; row < tree.getLeadSelectionRow(); row++) + { + if (checkMatch(row, typed)) + { + tree.setSelectionRow(row); + tree.scrollRowToVisible(row); + return; + } + } + } + + /** + * Check if the given tree row starts with this character + * + * @param row the tree row + * @param typed the typed char, must be converted to lowercase + * @return true if the given tree row starts with this character + */ + boolean checkMatch(int row, char typed) + { + TreePath path = treeState.getPathForRow(row); + String node = path.getLastPathComponent().toString(); + if (node.length() > 0) + { + char x = node.charAt(0); + if (typed == Character.toLowerCase(x)) + return true; + } + return false; + } + + /** + * Invoked when a key has been pressed. + * + * @param e the key pressed + */ + public void keyPressed(KeyEvent e) + { + // Nothing to do here. + } + + /** + * Invoked when a key has been released + * + * @param e the key released + */ + public void keyReleased(KeyEvent e) + { + // Nothing to do here. + } + } + + /** + * MouseListener is responsible for updating the selection based on mouse + * events. + */ + public class MouseHandler + extends MouseAdapter + implements MouseMotionListener + { + + /** + * If the cell has been selected on mouse press. + */ + private boolean selectedOnPress; + + /** + * Constructor + */ + public MouseHandler() + { + // Nothing to do here. + } + + /** + * Invoked when a mouse button has been pressed on a component. + * + * @param e is the mouse event that occured + */ + public void mousePressed(MouseEvent e) + { + if (! e.isConsumed()) + { + handleEvent(e); + selectedOnPress = true; + } + else + { + selectedOnPress = false; + } + } + + /** + * Invoked when a mouse button is pressed on a component and then dragged. + * MOUSE_DRAGGED events will continue to be delivered to the component where + * the drag originated until the mouse button is released (regardless of + * whether the mouse position is within the bounds of the component). + * + * @param e is the mouse event that occured + */ + public void mouseDragged(MouseEvent e) + { + // Nothing to do here. + } + + /** + * Invoked when the mouse button has been moved on a component (with no + * buttons no down). + * + * @param e the mouse event that occured + */ + public void mouseMoved(MouseEvent e) + { + // Nothing to do here. + } + + /** + * Invoked when a mouse button has been released on a component. + * + * @param e is the mouse event that occured + */ + public void mouseReleased(MouseEvent e) + { + if (! e.isConsumed() && ! selectedOnPress) + handleEvent(e); + } + + /** + * Handles press and release events. + * + * @param e the mouse event + */ + private void handleEvent(MouseEvent e) + { + if (tree != null && tree.isEnabled()) + { + // Maybe stop editing. + if (isEditing(tree) && tree.getInvokesStopCellEditing() + && ! stopEditing(tree)) + return; + + // Explicitly request focus. + tree.requestFocusInWindow(); + + int x = e.getX(); + int y = e.getY(); + TreePath path = getClosestPathForLocation(tree, x, y); + if (path != null) + { + Rectangle b = getPathBounds(tree, path); + if (y <= b.y + b.height) + { + if (SwingUtilities.isLeftMouseButton(e)) + checkForClickInExpandControl(path, x, y); + if (x > b.x && x <= b.x + b.width) + { + if (! startEditing(path, e)) + selectPathForEvent(path, e); + } + } + } + } + } + } + + /** + * MouseInputHandler handles passing all mouse events, including mouse motion + * events, until the mouse is released to the destination it is constructed + * with. + */ + public class MouseInputHandler + implements MouseInputListener + { + /** Source that events are coming from */ + protected Component source; + + /** Destination that receives all events. */ + protected Component destination; + + /** + * Constructor + * + * @param source that events are coming from + * @param destination that receives all events + * @param e is the event received + */ + public MouseInputHandler(Component source, Component destination, + MouseEvent e) + { + this.source = source; + this.destination = destination; + source.addMouseListener(this); + source.addMouseMotionListener(this); + dispatch(e); + } + + /** + * Invoked when the mouse button has been clicked (pressed and released) on + * a component. + * + * @param e mouse event that occured + */ + public void mouseClicked(MouseEvent e) + { + dispatch(e); + } + + /** + * Invoked when a mouse button has been pressed on a component. + * + * @param e mouse event that occured + */ + public void mousePressed(MouseEvent e) + { + // Nothing to do here. + } + + /** + * Invoked when a mouse button has been released on a component. + * + * @param e mouse event that occured + */ + public void mouseReleased(MouseEvent e) + { + dispatch(e); + removeFromSource(); + } + + /** + * Invoked when the mouse enters a component. + * + * @param e mouse event that occured + */ + public void mouseEntered(MouseEvent e) + { + if (! SwingUtilities.isLeftMouseButton(e)) + removeFromSource(); + } + + /** + * Invoked when the mouse exits a component. + * + * @param e mouse event that occured + */ + public void mouseExited(MouseEvent e) + { + if (! SwingUtilities.isLeftMouseButton(e)) + removeFromSource(); + } + + /** + * Invoked when a mouse button is pressed on a component and then dragged. + * MOUSE_DRAGGED events will continue to be delivered to the component where + * the drag originated until the mouse button is released (regardless of + * whether the mouse position is within the bounds of the component). + * + * @param e mouse event that occured + */ + public void mouseDragged(MouseEvent e) + { + dispatch(e); + } + + /** + * Invoked when the mouse cursor has been moved onto a component but no + * buttons have been pushed. + * + * @param e mouse event that occured + */ + public void mouseMoved(MouseEvent e) + { + removeFromSource(); + } + + /** + * Removes event from the source + */ + protected void removeFromSource() + { + if (source != null) + { + source.removeMouseListener(this); + source.removeMouseMotionListener(this); + } + source = null; + destination = null; + } + + /** + * Redispatches mouse events to the destination. + * + * @param e the mouse event to redispatch + */ + private void dispatch(MouseEvent e) + { + if (destination != null) + { + MouseEvent e2 = SwingUtilities.convertMouseEvent(source, e, + destination); + destination.dispatchEvent(e2); + } + } + } + + /** + * Class responsible for getting size of node, method is forwarded to + * BasicTreeUI method. X location does not include insets, that is handled in + * getPathBounds. + */ + public class NodeDimensionsHandler + extends AbstractLayoutCache.NodeDimensions + { + /** + * Constructor + */ + public NodeDimensionsHandler() + { + // Nothing to do here. + } + + /** + * Returns, by reference in bounds, the size and x origin to place value at. + * The calling method is responsible for determining the Y location. If + * bounds is null, a newly created Rectangle should be returned, otherwise + * the value should be placed in bounds and returned. + * + * @param cell the value to be represented + * @param row row being queried + * @param depth the depth of the row + * @param expanded true if row is expanded + * @param size a Rectangle containing the size needed to represent value + * @return containing the node dimensions, or null if node has no dimension + */ + public Rectangle getNodeDimensions(Object cell, int row, int depth, + boolean expanded, Rectangle size) + { + Dimension prefSize; + if (editingComponent != null && editingRow == row) + { + // Editing, ask editor for preferred size. + prefSize = editingComponent.getPreferredSize(); + int rowHeight = getRowHeight(); + if (rowHeight > 0 && rowHeight != prefSize.height) + prefSize.height = rowHeight; + } + else + { + // Not editing, ask renderer for preferred size. + Component rend = + currentCellRenderer.getTreeCellRendererComponent(tree, cell, + tree.isRowSelected(row), + expanded, + treeModel.isLeaf(cell), + row, false); + // Make sure the layout is valid. + rendererPane.add(rend); + rend.validate(); + prefSize = rend.getPreferredSize(); + } + if (size != null) + { + size.x = getRowX(row, depth); + // FIXME: This should be handled by the layout cache. + size.y = prefSize.height * row; + size.width = prefSize.width; + size.height = prefSize.height; + } + else + // FIXME: The y should be handled by the layout cache. + size = new Rectangle(getRowX(row, depth), prefSize.height * row, prefSize.width, + prefSize.height); + + return size; + } + + /** + * Returns the amount to indent the given row + * + * @return amount to indent the given row. + */ + protected int getRowX(int row, int depth) + { + return BasicTreeUI.this.getRowX(row, depth); + } + } // NodeDimensionsHandler + + /** + * PropertyChangeListener for the tree. Updates the appropriate variable, or + * TreeState, based on what changes. + */ + public class PropertyChangeHandler + implements PropertyChangeListener + { + + /** + * Constructor + */ + public PropertyChangeHandler() + { + // Nothing to do here. + } + + /** + * This method gets called when a bound property is changed. + * + * @param event A PropertyChangeEvent object describing the event source and + * the property that has changed. + */ + public void propertyChange(PropertyChangeEvent event) + { + String property = event.getPropertyName(); + if (property.equals(JTree.ROOT_VISIBLE_PROPERTY)) + { + validCachedPreferredSize = false; + treeState.setRootVisible(tree.isRootVisible()); + tree.repaint(); + } + else if (property.equals(JTree.SELECTION_MODEL_PROPERTY)) + { + treeSelectionModel = tree.getSelectionModel(); + treeSelectionModel.setRowMapper(treeState); + } + else if (property.equals(JTree.TREE_MODEL_PROPERTY)) + { + setModel(tree.getModel()); + } + else if (property.equals(JTree.CELL_RENDERER_PROPERTY)) + { + setCellRenderer(tree.getCellRenderer()); + // Update layout. + if (treeState != null) + treeState.invalidateSizes(); + } + else if (property.equals(JTree.EDITABLE_PROPERTY)) + setEditable(((Boolean) event.getNewValue()).booleanValue()); + + } + } + + /** + * Listener on the TreeSelectionModel, resets the row selection if any of the + * properties of the model change. + */ + public class SelectionModelPropertyChangeHandler + implements PropertyChangeListener + { + + /** + * Constructor + */ + public SelectionModelPropertyChangeHandler() + { + // Nothing to do here. + } + + /** + * This method gets called when a bound property is changed. + * + * @param event A PropertyChangeEvent object describing the event source and + * the property that has changed. + */ + public void propertyChange(PropertyChangeEvent event) + { + treeSelectionModel.resetRowSelection(); + } + } + + /** + * The action to cancel editing on this tree. + */ + public class TreeCancelEditingAction + extends AbstractAction + { + /** + * Creates the new tree cancel editing action. + * + * @param name the name of the action (used in toString). + */ + public TreeCancelEditingAction(String name) + { + super(name); + } + + /** + * Invoked when an action occurs, cancels the cell editing (if the + * tree cell is being edited). + * + * @param e event that occured + */ + public void actionPerformed(ActionEvent e) + { + if (isEnabled() && tree.isEditing()) + tree.cancelEditing(); + } + } + + /** + * Updates the TreeState in response to nodes expanding/collapsing. + */ + public class TreeExpansionHandler + implements TreeExpansionListener + { + + /** + * Constructor + */ + public TreeExpansionHandler() + { + // Nothing to do here. + } + + /** + * Called whenever an item in the tree has been expanded. + * + * @param event is the event that occured + */ + public void treeExpanded(TreeExpansionEvent event) + { + validCachedPreferredSize = false; + treeState.setExpandedState(event.getPath(), true); + // The maximal cell height may change + maxHeight = 0; + tree.revalidate(); + tree.repaint(); + } + + /** + * Called whenever an item in the tree has been collapsed. + * + * @param event is the event that occured + */ + public void treeCollapsed(TreeExpansionEvent event) + { + completeEditing(); + validCachedPreferredSize = false; + treeState.setExpandedState(event.getPath(), false); + // The maximal cell height may change + maxHeight = 0; + tree.revalidate(); + tree.repaint(); + } + } // TreeExpansionHandler + + /** + * TreeHomeAction is used to handle end/home actions. Scrolls either the first + * or last cell to be visible based on direction. + */ + public class TreeHomeAction + extends AbstractAction + { + + /** The direction, either home or end */ + protected int direction; + + /** + * Creates a new TreeHomeAction instance. + * + * @param dir the direction to go to, <code>-1</code> for home, + * <code>1</code> for end + * @param name the name of the action + */ + public TreeHomeAction(int dir, String name) + { + direction = dir; + putValue(Action.NAME, name); + } + + /** + * Invoked when an action occurs. + * + * @param e is the event that occured + */ + public void actionPerformed(ActionEvent e) + { + if (tree != null) + { + String command = (String) getValue(Action.NAME); + if (command.equals("selectFirst")) + { + ensureRowsAreVisible(0, 0); + tree.setSelectionInterval(0, 0); + } + if (command.equals("selectFirstChangeLead")) + { + ensureRowsAreVisible(0, 0); + tree.setLeadSelectionPath(getPathForRow(tree, 0)); + } + if (command.equals("selectFirstExtendSelection")) + { + ensureRowsAreVisible(0, 0); + TreePath anchorPath = tree.getAnchorSelectionPath(); + if (anchorPath == null) + tree.setSelectionInterval(0, 0); + else + { + int anchorRow = getRowForPath(tree, anchorPath); + tree.setSelectionInterval(0, anchorRow); + tree.setAnchorSelectionPath(anchorPath); + tree.setLeadSelectionPath(getPathForRow(tree, 0)); + } + } + else if (command.equals("selectLast")) + { + int end = getRowCount(tree) - 1; + ensureRowsAreVisible(end, end); + tree.setSelectionInterval(end, end); + } + else if (command.equals("selectLastChangeLead")) + { + int end = getRowCount(tree) - 1; + ensureRowsAreVisible(end, end); + tree.setLeadSelectionPath(getPathForRow(tree, end)); + } + else if (command.equals("selectLastExtendSelection")) + { + int end = getRowCount(tree) - 1; + ensureRowsAreVisible(end, end); + TreePath anchorPath = tree.getAnchorSelectionPath(); + if (anchorPath == null) + tree.setSelectionInterval(end, end); + else + { + int anchorRow = getRowForPath(tree, anchorPath); + tree.setSelectionInterval(end, anchorRow); + tree.setAnchorSelectionPath(anchorPath); + tree.setLeadSelectionPath(getPathForRow(tree, end)); + } + } + } + + // Ensure that the lead path is visible after the increment action. + tree.scrollPathToVisible(tree.getLeadSelectionPath()); + } + + /** + * Returns true if the action is enabled. + * + * @return true if the action is enabled. + */ + public boolean isEnabled() + { + return (tree != null) && tree.isEnabled(); + } + } + + /** + * TreeIncrementAction is used to handle up/down actions. Selection is moved + * up or down based on direction. + */ + public class TreeIncrementAction + extends AbstractAction + { + + /** + * Specifies the direction to adjust the selection by. + */ + protected int direction; + + /** + * Creates a new TreeIncrementAction. + * + * @param dir up or down, <code>-1</code> for up, <code>1</code> for down + * @param name is the name of the direction + */ + public TreeIncrementAction(int dir, String name) + { + direction = dir; + putValue(Action.NAME, name); + } + + /** + * Invoked when an action occurs. + * + * @param e is the event that occured + */ + public void actionPerformed(ActionEvent e) + { + TreePath currentPath = tree.getLeadSelectionPath(); + int currentRow; + + if (currentPath != null) + currentRow = treeState.getRowForPath(currentPath); + else + currentRow = 0; + + int rows = treeState.getRowCount(); + + int nextRow = currentRow + 1; + int prevRow = currentRow - 1; + boolean hasNext = nextRow < rows; + boolean hasPrev = prevRow >= 0 && rows > 0; + TreePath newPath; + String command = (String) getValue(Action.NAME); + + if (command.equals("selectPreviousChangeLead") && hasPrev) + { + newPath = treeState.getPathForRow(prevRow); + tree.setSelectionPath(newPath); + tree.setAnchorSelectionPath(newPath); + tree.setLeadSelectionPath(newPath); + } + else if (command.equals("selectPreviousExtendSelection") && hasPrev) + { + newPath = treeState.getPathForRow(prevRow); + + // If the new path is already selected, the selection shrinks, + // unselecting the previously current path. + if (tree.isPathSelected(newPath)) + tree.getSelectionModel().removeSelectionPath(currentPath); + + // This must be called in any case because it updates the model + // lead selection index. + tree.addSelectionPath(newPath); + tree.setLeadSelectionPath(newPath); + } + else if (command.equals("selectPrevious") && hasPrev) + { + newPath = treeState.getPathForRow(prevRow); + tree.setSelectionPath(newPath); + } + else if (command.equals("selectNext") && hasNext) + { + newPath = treeState.getPathForRow(nextRow); + tree.setSelectionPath(newPath); + } + else if (command.equals("selectNextExtendSelection") && hasNext) + { + newPath = treeState.getPathForRow(nextRow); + + // If the new path is already selected, the selection shrinks, + // unselecting the previously current path. + if (tree.isPathSelected(newPath)) + tree.getSelectionModel().removeSelectionPath(currentPath); + + // This must be called in any case because it updates the model + // lead selection index. + tree.addSelectionPath(newPath); + + tree.setLeadSelectionPath(newPath); + } + else if (command.equals("selectNextChangeLead") && hasNext) + { + newPath = treeState.getPathForRow(nextRow); + tree.setSelectionPath(newPath); + tree.setAnchorSelectionPath(newPath); + tree.setLeadSelectionPath(newPath); + } + + // Ensure that the lead path is visible after the increment action. + tree.scrollPathToVisible(tree.getLeadSelectionPath()); + } + + /** + * Returns true if the action is enabled. + * + * @return true if the action is enabled. + */ + public boolean isEnabled() + { + return (tree != null) && tree.isEnabled(); + } + } + + /** + * Forwards all TreeModel events to the TreeState. + */ + public class TreeModelHandler + implements TreeModelListener + { + /** + * Constructor + */ + public TreeModelHandler() + { + // Nothing to do here. + } + + /** + * Invoked after a node (or a set of siblings) has changed in some way. The + * node(s) have not changed locations in the tree or altered their children + * arrays, but other attributes have changed and may affect presentation. + * Example: the name of a file has changed, but it is in the same location + * in the file system. To indicate the root has changed, childIndices and + * children will be null. Use e.getPath() to get the parent of the changed + * node(s). e.getChildIndices() returns the index(es) of the changed + * node(s). + * + * @param e is the event that occured + */ + public void treeNodesChanged(TreeModelEvent e) + { + validCachedPreferredSize = false; + treeState.treeNodesChanged(e); + tree.repaint(); + } + + /** + * Invoked after nodes have been inserted into the tree. Use e.getPath() to + * get the parent of the new node(s). e.getChildIndices() returns the + * index(es) of the new node(s) in ascending order. + * + * @param e is the event that occured + */ + public void treeNodesInserted(TreeModelEvent e) + { + validCachedPreferredSize = false; + treeState.treeNodesInserted(e); + tree.repaint(); + } + + /** + * Invoked after nodes have been removed from the tree. Note that if a + * subtree is removed from the tree, this method may only be invoked once + * for the root of the removed subtree, not once for each individual set of + * siblings removed. Use e.getPath() to get the former parent of the deleted + * node(s). e.getChildIndices() returns, in ascending order, the index(es) + * the node(s) had before being deleted. + * + * @param e is the event that occured + */ + public void treeNodesRemoved(TreeModelEvent e) + { + validCachedPreferredSize = false; + treeState.treeNodesRemoved(e); + tree.repaint(); + } + + /** + * Invoked after the tree has drastically changed structure from a given + * node down. If the path returned by e.getPath() is of length one and the + * first element does not identify the current root node the first element + * should become the new root of the tree. Use e.getPath() to get the path + * to the node. e.getChildIndices() returns null. + * + * @param e is the event that occured + */ + public void treeStructureChanged(TreeModelEvent e) + { + if (e.getPath().length == 1 + && ! e.getPath()[0].equals(treeModel.getRoot())) + tree.expandPath(new TreePath(treeModel.getRoot())); + validCachedPreferredSize = false; + treeState.treeStructureChanged(e); + tree.repaint(); + } + } // TreeModelHandler + + /** + * TreePageAction handles page up and page down events. + */ + public class TreePageAction + extends AbstractAction + { + /** Specifies the direction to adjust the selection by. */ + protected int direction; + + /** + * Constructor + * + * @param direction up or down + * @param name is the name of the direction + */ + public TreePageAction(int direction, String name) + { + this.direction = direction; + putValue(Action.NAME, name); + } + + /** + * Invoked when an action occurs. + * + * @param e is the event that occured + */ + public void actionPerformed(ActionEvent e) + { + String command = (String) getValue(Action.NAME); + boolean extendSelection = command.equals("scrollUpExtendSelection") + || command.equals("scrollDownExtendSelection"); + boolean changeSelection = command.equals("scrollUpChangeSelection") + || command.equals("scrollDownChangeSelection"); + + // Disable change lead, unless we are in discontinuous mode. + if (!extendSelection && !changeSelection + && tree.getSelectionModel().getSelectionMode() != + TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION) + { + changeSelection = true; + } + + int rowCount = getRowCount(tree); + if (rowCount > 0 && treeSelectionModel != null) + { + Dimension maxSize = tree.getSize(); + TreePath lead = tree.getLeadSelectionPath(); + TreePath newPath = null; + Rectangle visible = tree.getVisibleRect(); + if (direction == -1) // The RI handles -1 as up. + { + newPath = getClosestPathForLocation(tree, visible.x, visible.y); + if (newPath.equals(lead)) // Corner case, adjust one page up. + { + visible.y = Math.max(0, visible.y - visible.height); + newPath = getClosestPathForLocation(tree, visible.x, + visible.y); + } + } + else // +1 is down. + { + visible.y = Math.min(maxSize.height, + visible.y + visible.height - 1); + newPath = getClosestPathForLocation(tree, visible.x, visible.y); + if (newPath.equals(lead)) // Corner case, adjust one page down. + { + visible.y = Math.min(maxSize.height, + visible.y + visible.height - 1); + newPath = getClosestPathForLocation(tree, visible.x, + visible.y); + } + } + + // Determine new visible rect. + Rectangle newVisible = getPathBounds(tree, newPath); + newVisible.x = visible.x; + newVisible.width = visible.width; + if (direction == -1) + { + newVisible.height = visible.height; + } + else + { + newVisible.y -= visible.height - newVisible.height; + newVisible.height = visible.height; + } + + if (extendSelection) + { + // Extend selection. + TreePath anchorPath = tree.getAnchorSelectionPath(); + if (anchorPath == null) + { + tree.setSelectionPath(newPath); + } + else + { + int newIndex = getRowForPath(tree, newPath); + int anchorIndex = getRowForPath(tree, anchorPath); + tree.setSelectionInterval(Math.min(anchorIndex, newIndex), + Math.max(anchorIndex, newIndex)); + tree.setAnchorSelectionPath(anchorPath); + tree.setLeadSelectionPath(newPath); + } + } + else if (changeSelection) + { + tree.setSelectionPath(newPath); + } + else // Change lead. + { + tree.setLeadSelectionPath(newPath); + } + + tree.scrollRectToVisible(newVisible); + } + } + + /** + * Returns true if the action is enabled. + * + * @return true if the action is enabled. + */ + public boolean isEnabled() + { + return (tree != null) && tree.isEnabled(); + } + } // TreePageAction + + /** + * Listens for changes in the selection model and updates the display + * accordingly. + */ + public class TreeSelectionHandler + implements TreeSelectionListener + { + /** + * Constructor + */ + public TreeSelectionHandler() + { + // Nothing to do here. + } + + /** + * Messaged when the selection changes in the tree we're displaying for. + * Stops editing, messages super and displays the changed paths. + * + * @param event the event that characterizes the change. + */ + public void valueChanged(TreeSelectionEvent event) + { + completeEditing(); + + TreePath op = event.getOldLeadSelectionPath(); + TreePath np = event.getNewLeadSelectionPath(); + + // Repaint of the changed lead selection path. + if (op != np) + { + Rectangle o = treeState.getBounds(event.getOldLeadSelectionPath(), + new Rectangle()); + Rectangle n = treeState.getBounds(event.getNewLeadSelectionPath(), + new Rectangle()); + + if (o != null) + tree.repaint(o); + if (n != null) + tree.repaint(n); + } + } + } // TreeSelectionHandler + + /** + * For the first selected row expandedness will be toggled. + */ + public class TreeToggleAction + extends AbstractAction + { + /** + * Creates a new TreeToggleAction. + * + * @param name is the name of <code>Action</code> field + */ + public TreeToggleAction(String name) + { + putValue(Action.NAME, name); + } + + /** + * Invoked when an action occurs. + * + * @param e the event that occured + */ + public void actionPerformed(ActionEvent e) + { + int selected = tree.getLeadSelectionRow(); + if (selected != -1 && isLeaf(selected)) + { + TreePath anchorPath = tree.getAnchorSelectionPath(); + TreePath leadPath = tree.getLeadSelectionPath(); + toggleExpandState(getPathForRow(tree, selected)); + // Need to do this, so that the toggling doesn't mess up the lead + // and anchor. + tree.setLeadSelectionPath(leadPath); + tree.setAnchorSelectionPath(anchorPath); + + // Ensure that the lead path is visible after the increment action. + tree.scrollPathToVisible(tree.getLeadSelectionPath()); + } + } + + /** + * Returns true if the action is enabled. + * + * @return true if the action is enabled, false otherwise + */ + public boolean isEnabled() + { + return (tree != null) && tree.isEnabled(); + } + } // TreeToggleAction + + /** + * TreeTraverseAction is the action used for left/right keys. Will toggle the + * expandedness of a node, as well as potentially incrementing the selection. + */ + public class TreeTraverseAction + extends AbstractAction + { + /** + * Determines direction to traverse, 1 means expand, -1 means collapse. + */ + protected int direction; + + /** + * Constructor + * + * @param direction to traverse + * @param name is the name of the direction + */ + public TreeTraverseAction(int direction, String name) + { + this.direction = direction; + putValue(Action.NAME, name); + } + + /** + * Invoked when an action occurs. + * + * @param e the event that occured + */ + public void actionPerformed(ActionEvent e) + { + TreePath current = tree.getLeadSelectionPath(); + if (current == null) + return; + + String command = (String) getValue(Action.NAME); + if (command.equals("selectParent")) + { + if (current == null) + return; + + if (tree.isExpanded(current)) + { + tree.collapsePath(current); + } + else + { + // If the node is not expanded (also, if it is a leaf node), + // we just select the parent. We do not select the root if it + // is not visible. + TreePath parent = current.getParentPath(); + if (parent != null && + ! (parent.getPathCount() == 1 && ! tree.isRootVisible())) + tree.setSelectionPath(parent); + } + } + else if (command.equals("selectChild")) + { + Object node = current.getLastPathComponent(); + int nc = treeModel.getChildCount(node); + if (nc == 0 || treeState.isExpanded(current)) + { + // If the node is leaf or it is already expanded, + // we just select the next row. + int nextRow = tree.getLeadSelectionRow() + 1; + if (nextRow <= tree.getRowCount()) + tree.setSelectionRow(nextRow); + } + else + { + tree.expandPath(current); + } + } + + // Ensure that the lead path is visible after the increment action. + tree.scrollPathToVisible(tree.getLeadSelectionPath()); + } + + /** + * Returns true if the action is enabled. + * + * @return true if the action is enabled, false otherwise + */ + public boolean isEnabled() + { + return (tree != null) && tree.isEnabled(); + } + } + + /** + * Returns true if the LookAndFeel implements the control icons. Package + * private for use in inner classes. + * + * @returns true if there are control icons + */ + boolean hasControlIcons() + { + if (expandedIcon != null || collapsedIcon != null) + return true; + return false; + } + + /** + * Returns control icon. It is null if the LookAndFeel does not implements the + * control icons. Package private for use in inner classes. + * + * @return control icon if it exists. + */ + Icon getCurrentControlIcon(TreePath path) + { + if (hasControlIcons()) + { + if (tree.isExpanded(path)) + return expandedIcon; + else + return collapsedIcon; + } + else + { + if (nullIcon == null) + nullIcon = new Icon() + { + public int getIconHeight() + { + return 0; + } + + public int getIconWidth() + { + return 0; + } + + public void paintIcon(Component c, Graphics g, int x, int y) + { + // No action here. + } + }; + return nullIcon; + } + } + + /** + * Returns the parent of the current node + * + * @param root is the root of the tree + * @param node is the current node + * @return is the parent of the current node + */ + Object getParent(Object root, Object node) + { + if (root == null || node == null || root.equals(node)) + return null; + + if (node instanceof TreeNode) + return ((TreeNode) node).getParent(); + return findNode(root, node); + } + + /** + * Recursively checks the tree for the specified node, starting at the root. + * + * @param root is starting node to start searching at. + * @param node is the node to search for + * @return the parent node of node + */ + private Object findNode(Object root, Object node) + { + if (! treeModel.isLeaf(root) && ! root.equals(node)) + { + int size = treeModel.getChildCount(root); + for (int j = 0; j < size; j++) + { + Object child = treeModel.getChild(root, j); + if (node.equals(child)) + return root; + + Object n = findNode(child, node); + if (n != null) + return n; + } + } + return null; + } + + /** + * Selects the specified path in the tree depending on modes. Package private + * for use in inner classes. + * + * @param tree is the tree we are selecting the path in + * @param path is the path we are selecting + */ + void selectPath(JTree tree, TreePath path) + { + if (path != null) + { + tree.setSelectionPath(path); + tree.setLeadSelectionPath(path); + tree.makeVisible(path); + tree.scrollPathToVisible(path); + } + } + + /** + * Returns the path from node to the root. Package private for use in inner + * classes. + * + * @param node the node to get the path to + * @param depth the depth of the tree to return a path for + * @return an array of tree nodes that represent the path to node. + */ + Object[] getPathToRoot(Object node, int depth) + { + if (node == null) + { + if (depth == 0) + return null; + + return new Object[depth]; + } + + Object[] path = getPathToRoot(getParent(treeModel.getRoot(), node), + depth + 1); + path[path.length - depth - 1] = node; + return path; + } + + /** + * Draws a vertical line using the given graphic context + * + * @param g is the graphic context + * @param c is the component the new line will belong to + * @param x is the horizonal position + * @param top specifies the top of the line + * @param bottom specifies the bottom of the line + */ + protected void paintVerticalLine(Graphics g, JComponent c, int x, int top, + int bottom) + { + // FIXME: Check if drawing a dashed line or not. + g.setColor(getHashColor()); + g.drawLine(x, top, x, bottom); + } + + /** + * Draws a horizontal line using the given graphic context + * + * @param g is the graphic context + * @param c is the component the new line will belong to + * @param y is the vertical position + * @param left specifies the left point of the line + * @param right specifies the right point of the line + */ + protected void paintHorizontalLine(Graphics g, JComponent c, int y, int left, + int right) + { + // FIXME: Check if drawing a dashed line or not. + g.setColor(getHashColor()); + g.drawLine(left, y, right, y); + } + + /** + * Draws an icon at around a specific position + * + * @param c is the component the new line will belong to + * @param g is the graphic context + * @param icon is the icon which will be drawn + * @param x is the center position in x-direction + * @param y is the center position in y-direction + */ + protected void drawCentered(Component c, Graphics g, Icon icon, int x, int y) + { + x -= icon.getIconWidth() / 2; + y -= icon.getIconHeight() / 2; + + if (x < 0) + x = 0; + if (y < 0) + y = 0; + + icon.paintIcon(c, g, x, y); + } + + /** + * Draws a dashed horizontal line. + * + * @param g - the graphics configuration. + * @param y - the y location to start drawing at + * @param x1 - the x location to start drawing at + * @param x2 - the x location to finish drawing at + */ + protected void drawDashedHorizontalLine(Graphics g, int y, int x1, int x2) + { + g.setColor(getHashColor()); + for (int i = x1; i < x2; i += 2) + g.drawLine(i, y, i + 1, y); + } + + /** + * Draws a dashed vertical line. + * + * @param g - the graphics configuration. + * @param x - the x location to start drawing at + * @param y1 - the y location to start drawing at + * @param y2 - the y location to finish drawing at + */ + protected void drawDashedVerticalLine(Graphics g, int x, int y1, int y2) + { + g.setColor(getHashColor()); + for (int i = y1; i < y2; i += 2) + g.drawLine(x, i, x, i + 1); + } + + /** + * Paints the expand (toggle) part of a row. The receiver should NOT modify + * clipBounds, or insets. + * + * @param g - the graphics configuration + * @param clipBounds - + * @param insets - + * @param bounds - bounds of expand control + * @param path - path to draw control for + * @param row - row to draw control for + * @param isExpanded - is the row expanded + * @param hasBeenExpanded - has the row already been expanded + * @param isLeaf - is the path a leaf + */ + protected void paintExpandControl(Graphics g, Rectangle clipBounds, + Insets insets, Rectangle bounds, + TreePath path, int row, boolean isExpanded, + boolean hasBeenExpanded, boolean isLeaf) + { + if (shouldPaintExpandControl(path, row, isExpanded, hasBeenExpanded, isLeaf)) + { + Icon icon = getCurrentControlIcon(path); + int iconW = icon.getIconWidth(); + int x = bounds.x - iconW - gap; + icon.paintIcon(tree, g, x, bounds.y + bounds.height / 2 + - icon.getIconHeight() / 2); + } + } + + /** + * Paints the horizontal part of the leg. The receiver should NOT modify + * clipBounds, or insets. NOTE: parentRow can be -1 if the root is not + * visible. + * + * @param g - the graphics configuration + * @param clipBounds - + * @param insets - + * @param bounds - bounds of the cell + * @param path - path to draw leg for + * @param row - row to start drawing at + * @param isExpanded - is the row expanded + * @param hasBeenExpanded - has the row already been expanded + * @param isLeaf - is the path a leaf + */ + protected void paintHorizontalPartOfLeg(Graphics g, Rectangle clipBounds, + Insets insets, Rectangle bounds, + TreePath path, int row, + boolean isExpanded, + boolean hasBeenExpanded, + boolean isLeaf) + { + if (row != 0) + { + paintHorizontalLine(g, tree, bounds.y + bounds.height / 2, + bounds.x - leftChildIndent - gap, bounds.x - gap); + } + } + + /** + * Paints the vertical part of the leg. The receiver should NOT modify + * clipBounds, insets. + * + * @param g - the graphics configuration. + * @param clipBounds - + * @param insets - + * @param path - the path to draw the vertical part for. + */ + protected void paintVerticalPartOfLeg(Graphics g, Rectangle clipBounds, + Insets insets, TreePath path) + { + Rectangle bounds = getPathBounds(tree, path); + TreePath parent = path.getParentPath(); + + boolean paintLine; + if (isRootVisible()) + paintLine = parent != null; + else + paintLine = parent != null && parent.getPathCount() > 1; + if (paintLine) + { + Rectangle parentBounds = getPathBounds(tree, parent); + paintVerticalLine(g, tree, parentBounds.x + 2 * gap, + parentBounds.y + parentBounds.height / 2, + bounds.y + bounds.height / 2); + } + } + + /** + * Paints the renderer part of a row. The receiver should NOT modify + * clipBounds, or insets. + * + * @param g - the graphics configuration + * @param clipBounds - + * @param insets - + * @param bounds - bounds of expand control + * @param path - path to draw control for + * @param row - row to draw control for + * @param isExpanded - is the row expanded + * @param hasBeenExpanded - has the row already been expanded + * @param isLeaf - is the path a leaf + */ + protected void paintRow(Graphics g, Rectangle clipBounds, Insets insets, + Rectangle bounds, TreePath path, int row, + boolean isExpanded, boolean hasBeenExpanded, + boolean isLeaf) + { + boolean selected = tree.isPathSelected(path); + boolean hasIcons = false; + Object node = path.getLastPathComponent(); + + paintExpandControl(g, clipBounds, insets, bounds, path, row, isExpanded, + hasBeenExpanded, isLeaf); + + TreeCellRenderer dtcr = currentCellRenderer; + + boolean focused = false; + if (treeSelectionModel != null) + focused = treeSelectionModel.getLeadSelectionRow() == row + && tree.isFocusOwner(); + + Component c = dtcr.getTreeCellRendererComponent(tree, node, selected, + isExpanded, isLeaf, row, + focused); + + rendererPane.paintComponent(g, c, c.getParent(), bounds); + } + + /** + * Prepares for the UI to uninstall. + */ + protected void prepareForUIUninstall() + { + // Nothing to do here yet. + } + + /** + * Returns true if the expand (toggle) control should be drawn for the + * specified row. + * + * @param path - current path to check for. + * @param row - current row to check for. + * @param isExpanded - true if the path is expanded + * @param hasBeenExpanded - true if the path has been expanded already + * @param isLeaf - true if the row is a lead + */ + protected boolean shouldPaintExpandControl(TreePath path, int row, + boolean isExpanded, + boolean hasBeenExpanded, + boolean isLeaf) + { + Object node = path.getLastPathComponent(); + return ! isLeaf && hasControlIcons(); + } + + /** + * Returns the amount to indent the given row + * + * @return amount to indent the given row. + */ + protected int getRowX(int row, int depth) + { + return depth * totalChildIndent; + } +} // BasicTreeUI diff --git a/libjava/classpath/javax/swing/plaf/basic/BasicViewportUI.java b/libjava/classpath/javax/swing/plaf/basic/BasicViewportUI.java new file mode 100644 index 000000000..11c7d6390 --- /dev/null +++ b/libjava/classpath/javax/swing/plaf/basic/BasicViewportUI.java @@ -0,0 +1,75 @@ +/* BasicViewportUI.java -- + Copyright (C) 2002, 2004 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 javax.swing.JComponent; +import javax.swing.LookAndFeel; +import javax.swing.plaf.ComponentUI; +import javax.swing.plaf.ViewportUI; + +public class BasicViewportUI extends ViewportUI +{ + protected void installDefaults(JComponent c) + { + c.setOpaque(true); + LookAndFeel.installColorsAndFont(c, "Viewport.background", + "Viewport.foreground", "Viewport.font"); + } + protected void uninstallDefaults(JComponent c) + { + // TODO: Implement this properly. + } + + public static ComponentUI createUI(JComponent c) + { + return new BasicViewportUI(); + } + + public void installUI(JComponent c) + { + super.installUI(c); + installDefaults(c); + } + + public void uninstallUI(JComponent c) + { + super.uninstallUI(c); + uninstallDefaults(c); + } +} diff --git a/libjava/classpath/javax/swing/plaf/basic/ComboPopup.java b/libjava/classpath/javax/swing/plaf/basic/ComboPopup.java new file mode 100644 index 000000000..8bdcc51b0 --- /dev/null +++ b/libjava/classpath/javax/swing/plaf/basic/ComboPopup.java @@ -0,0 +1,103 @@ +/* ComboPopup.java -- + Copyright (C) 2004 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 java.awt.event.KeyListener; +import java.awt.event.MouseListener; +import java.awt.event.MouseMotionListener; + +import javax.swing.JList; + +public interface ComboPopup +{ + /** + * This method display popup menu containing list of JComboBox's items to + * the screen + */ + void show(); + + /** + * This method hides popup menu with list of JComboBox's item from the + * screen + */ + void hide(); + + /** + * Retursn true if popup menu with JComboBOx's item is currently visible on + * the screen and false otherwise + * + * @return true if JComboBox's popup menu with list of items is currently + * visible on the screen and false otherwise. + */ + boolean isVisible(); + + /** + * Return JList that is used to draw cells of the JComboBox. + * + * @return JList that is used to draw cells of the JcomboBox + */ + JList getList(); + + /** + * This method returns MouseListener that listen's to mouse events occuring + * in the combo box + * + * @return MouseListenere + */ + MouseListener getMouseListener(); + + /** + * This method returns MouseListener that listen's to mouse events occuring + * in the combo box. + * + * @return MouseMotionListener + */ + MouseMotionListener getMouseMotionListener(); + + /** + * This method returns KeyListener that listen's to key events occuring in + * the combo box. + * + * @return KeyListener + */ + KeyListener getKeyListener(); + + /* This method removes any listeners that were installed */ + void uninstallingUI(); +} diff --git a/libjava/classpath/javax/swing/plaf/basic/DefaultMenuLayout.java b/libjava/classpath/javax/swing/plaf/basic/DefaultMenuLayout.java new file mode 100644 index 000000000..9760e82a6 --- /dev/null +++ b/libjava/classpath/javax/swing/plaf/basic/DefaultMenuLayout.java @@ -0,0 +1,78 @@ +/* DefaultMenuLayout.java + Copyright (C) 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 java.awt.Container; +import java.awt.Dimension; + +import javax.swing.BoxLayout; +import javax.swing.plaf.UIResource; + +/** + * The LayoutManager that is used in PopupMenus. This is a derived from + * {@link BoxLayout}. + * + * @author Roman Kennke (roman@kennke.org) + */ +public class DefaultMenuLayout + extends BoxLayout + implements UIResource +{ + + /** + * Creates a new instance of DefaultMenuLayout. + * + * @param target the component that is laid out + * @param axis the axis along which the component is laid out + */ + public DefaultMenuLayout(Container target, int axis) + { + super(target, axis); + } + + /** + * Returns the preferred size for the layout of the menu. + * + * @param target the Container which's preferred size we calculate + */ + public Dimension preferredLayoutSize(Container target) + { + return super.preferredLayoutSize(target); + } + +} diff --git a/libjava/classpath/javax/swing/plaf/basic/SharedUIDefaults.java b/libjava/classpath/javax/swing/plaf/basic/SharedUIDefaults.java new file mode 100644 index 000000000..34278052b --- /dev/null +++ b/libjava/classpath/javax/swing/plaf/basic/SharedUIDefaults.java @@ -0,0 +1,91 @@ +/* SharedUIDefaults.java -- Manages shared instances for UIDefaults + Copyright (C) 2006 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 java.awt.Color; +import java.util.HashMap; + +import javax.swing.UIManager; + +/** + * Manages shared instances for UI defaults. For example, all Swing components + * of one type usually share one InputMap/ActionMap pair. In order to avoid + * duplication of such objects we store them in a Map here. + * + * @author Roman Kennke (kennke@aicas.com) + */ +public class SharedUIDefaults +{ + + /** + * Stores the shared instances, indexed by their UI names + * (i.e. "TextField.InputMap"). + */ + private static HashMap sharedDefaults = new HashMap(); + + /** + * Returns a shared UI defaults object. + * + * @param key the key for the shared object + * + * @return a shared UI defaults object for the specified key + */ + static Object get(String key) + { + Object o = sharedDefaults.get(key); + if (o == null) + { + o = UIManager.get(key); + sharedDefaults.put(key, o); + } + return o; + } + + /** + * Returns a shared UI color. + * + * @param key the key + * + * @return the shared color instance + */ + static Color getColor(String key) + { + return (Color) get(key); + } +} diff --git a/libjava/classpath/javax/swing/plaf/basic/doc-files/BasicBorders-1.png b/libjava/classpath/javax/swing/plaf/basic/doc-files/BasicBorders-1.png Binary files differnew file mode 100644 index 000000000..daeb16c78 --- /dev/null +++ b/libjava/classpath/javax/swing/plaf/basic/doc-files/BasicBorders-1.png diff --git a/libjava/classpath/javax/swing/plaf/basic/doc-files/BasicBorders-2.png b/libjava/classpath/javax/swing/plaf/basic/doc-files/BasicBorders-2.png Binary files differnew file mode 100644 index 000000000..60f6a8abe --- /dev/null +++ b/libjava/classpath/javax/swing/plaf/basic/doc-files/BasicBorders-2.png diff --git a/libjava/classpath/javax/swing/plaf/basic/doc-files/BasicBorders.ButtonBorder-1.png b/libjava/classpath/javax/swing/plaf/basic/doc-files/BasicBorders.ButtonBorder-1.png Binary files differnew file mode 100644 index 000000000..54047dcc1 --- /dev/null +++ b/libjava/classpath/javax/swing/plaf/basic/doc-files/BasicBorders.ButtonBorder-1.png diff --git a/libjava/classpath/javax/swing/plaf/basic/doc-files/BasicBorders.FieldBorder-1.png b/libjava/classpath/javax/swing/plaf/basic/doc-files/BasicBorders.FieldBorder-1.png Binary files differnew file mode 100644 index 000000000..7c8991106 --- /dev/null +++ b/libjava/classpath/javax/swing/plaf/basic/doc-files/BasicBorders.FieldBorder-1.png diff --git a/libjava/classpath/javax/swing/plaf/basic/doc-files/BasicBorders.MarginBorder-1.png b/libjava/classpath/javax/swing/plaf/basic/doc-files/BasicBorders.MarginBorder-1.png Binary files differnew file mode 100644 index 000000000..a3841baac --- /dev/null +++ b/libjava/classpath/javax/swing/plaf/basic/doc-files/BasicBorders.MarginBorder-1.png diff --git a/libjava/classpath/javax/swing/plaf/basic/doc-files/BasicBorders.MenuBarBorder-1.png b/libjava/classpath/javax/swing/plaf/basic/doc-files/BasicBorders.MenuBarBorder-1.png Binary files differnew file mode 100644 index 000000000..13a9fa4e9 --- /dev/null +++ b/libjava/classpath/javax/swing/plaf/basic/doc-files/BasicBorders.MenuBarBorder-1.png diff --git a/libjava/classpath/javax/swing/plaf/basic/doc-files/BasicBorders.RadioButtonBorder-1.png b/libjava/classpath/javax/swing/plaf/basic/doc-files/BasicBorders.RadioButtonBorder-1.png Binary files differnew file mode 100644 index 000000000..a6408ec7e --- /dev/null +++ b/libjava/classpath/javax/swing/plaf/basic/doc-files/BasicBorders.RadioButtonBorder-1.png diff --git a/libjava/classpath/javax/swing/plaf/basic/doc-files/BasicBorders.SplitPaneBorder-1.png b/libjava/classpath/javax/swing/plaf/basic/doc-files/BasicBorders.SplitPaneBorder-1.png Binary files differnew file mode 100644 index 000000000..db283c29a --- /dev/null +++ b/libjava/classpath/javax/swing/plaf/basic/doc-files/BasicBorders.SplitPaneBorder-1.png diff --git a/libjava/classpath/javax/swing/plaf/basic/doc-files/BasicBorders.SplitPaneBorder-2.png b/libjava/classpath/javax/swing/plaf/basic/doc-files/BasicBorders.SplitPaneBorder-2.png Binary files differnew file mode 100644 index 000000000..65381bd8b --- /dev/null +++ b/libjava/classpath/javax/swing/plaf/basic/doc-files/BasicBorders.SplitPaneBorder-2.png diff --git a/libjava/classpath/javax/swing/plaf/basic/doc-files/BasicBorders.SplitPaneDividerBorder-1.png b/libjava/classpath/javax/swing/plaf/basic/doc-files/BasicBorders.SplitPaneDividerBorder-1.png Binary files differnew file mode 100644 index 000000000..c22763a97 --- /dev/null +++ b/libjava/classpath/javax/swing/plaf/basic/doc-files/BasicBorders.SplitPaneDividerBorder-1.png diff --git a/libjava/classpath/javax/swing/plaf/basic/doc-files/BasicBorders.ToggleButtonBorder-1.png b/libjava/classpath/javax/swing/plaf/basic/doc-files/BasicBorders.ToggleButtonBorder-1.png Binary files differnew file mode 100644 index 000000000..f898bee8f --- /dev/null +++ b/libjava/classpath/javax/swing/plaf/basic/doc-files/BasicBorders.ToggleButtonBorder-1.png diff --git a/libjava/classpath/javax/swing/plaf/basic/doc-files/BasicGraphicsUtils-1.png b/libjava/classpath/javax/swing/plaf/basic/doc-files/BasicGraphicsUtils-1.png Binary files differnew file mode 100644 index 000000000..99f8c6ec4 --- /dev/null +++ b/libjava/classpath/javax/swing/plaf/basic/doc-files/BasicGraphicsUtils-1.png diff --git a/libjava/classpath/javax/swing/plaf/basic/doc-files/BasicGraphicsUtils-2.png b/libjava/classpath/javax/swing/plaf/basic/doc-files/BasicGraphicsUtils-2.png Binary files differnew file mode 100644 index 000000000..59d9a6192 --- /dev/null +++ b/libjava/classpath/javax/swing/plaf/basic/doc-files/BasicGraphicsUtils-2.png diff --git a/libjava/classpath/javax/swing/plaf/basic/doc-files/BasicGraphicsUtils-3.png b/libjava/classpath/javax/swing/plaf/basic/doc-files/BasicGraphicsUtils-3.png Binary files differnew file mode 100644 index 000000000..5b0971c16 --- /dev/null +++ b/libjava/classpath/javax/swing/plaf/basic/doc-files/BasicGraphicsUtils-3.png diff --git a/libjava/classpath/javax/swing/plaf/basic/doc-files/BasicGraphicsUtils-4.png b/libjava/classpath/javax/swing/plaf/basic/doc-files/BasicGraphicsUtils-4.png Binary files differnew file mode 100644 index 000000000..ceba0b6e0 --- /dev/null +++ b/libjava/classpath/javax/swing/plaf/basic/doc-files/BasicGraphicsUtils-4.png diff --git a/libjava/classpath/javax/swing/plaf/basic/doc-files/BasicGraphicsUtils-5.png b/libjava/classpath/javax/swing/plaf/basic/doc-files/BasicGraphicsUtils-5.png Binary files differnew file mode 100644 index 000000000..fa3055f8c --- /dev/null +++ b/libjava/classpath/javax/swing/plaf/basic/doc-files/BasicGraphicsUtils-5.png diff --git a/libjava/classpath/javax/swing/plaf/basic/doc-files/BasicGraphicsUtils-6.png b/libjava/classpath/javax/swing/plaf/basic/doc-files/BasicGraphicsUtils-6.png Binary files differnew file mode 100644 index 000000000..c760313e0 --- /dev/null +++ b/libjava/classpath/javax/swing/plaf/basic/doc-files/BasicGraphicsUtils-6.png diff --git a/libjava/classpath/javax/swing/plaf/basic/doc-files/BasicGraphicsUtils-7.png b/libjava/classpath/javax/swing/plaf/basic/doc-files/BasicGraphicsUtils-7.png Binary files differnew file mode 100644 index 000000000..6a557a044 --- /dev/null +++ b/libjava/classpath/javax/swing/plaf/basic/doc-files/BasicGraphicsUtils-7.png diff --git a/libjava/classpath/javax/swing/plaf/basic/package.html b/libjava/classpath/javax/swing/plaf/basic/package.html new file mode 100644 index 000000000..700c8cdbc --- /dev/null +++ b/libjava/classpath/javax/swing/plaf/basic/package.html @@ -0,0 +1,46 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"> +<!-- package.html - describes classes in javax.swing.plaf.basic package. + Copyright (C) 2002, 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. --> + +<html> +<head><title>GNU Classpath - javax.swing.plaf.basic</title></head> + +<body> +<p>Provides a "basic" look and feel implementation.</p> + +</body> +</html> |