--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/java/util/org/opensolaris/os/vp/util/swing/WrappingText.java Fri Feb 20 13:56:20 2009 -0500
@@ -0,0 +1,334 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+
+/*
+ * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+package org.opensolaris.os.vp.util.swing;
+
+import java.awt.*;
+import java.util.ArrayList;
+import javax.swing.*;
+import org.opensolaris.os.vp.util.misc.TextUtil;
+
+/**
+ * The {@code WrappingText} class displays multi-line text, wrapping words
+ * at whitespace.
+ */
+@SuppressWarnings({"serial"})
+public class WrappingText extends JComponent {
+ //
+ // Instance data
+ //
+
+ private String text;
+ private int prefWidth = -1;
+ private boolean prefWidthIsStatic;
+
+ //
+ // Constructors
+ //
+
+ public WrappingText(String text, int columns) {
+ setText(text);
+ setFont(UIManager.getFont("TextField.font"));
+ setForeground(UIManager.getColor("Label.foreground"));
+ setBackground(UIManager.getColor("Label.background"));
+ setPreferredWidthInColumns(columns);
+
+ setOpaque(false);
+ }
+
+ public WrappingText(String text) {
+ this(text, -1);
+ }
+
+ public WrappingText() {
+ this("");
+ }
+
+ public WrappingText(int columns) {
+ this("", -1);
+ }
+
+ //
+ // Component methods
+ //
+
+ @Override
+ public void setBounds(int x, int y, int width, int height) {
+ super.setBounds(x, y, width, height);
+ setPreferredWidth(width);
+ }
+
+ @Override
+ public void setBounds(Rectangle r) {
+ super.setBounds(r);
+ setPreferredWidth(r.width);
+ }
+
+ @Override
+ public Dimension getMinimumSize() {
+ return getPreferredSize();
+ }
+
+ @Override
+ public Dimension getPreferredSize() {
+ if (isPreferredSizeSet()) {
+ return super.getPreferredSize();
+ }
+
+ Insets insets = getInsets();
+ FontMetrics metrics = getFontMetrics(getFont());
+
+ String[] lines = getLines(prefWidth < 0 ?
+ Integer.MAX_VALUE : prefWidth - insets.left - insets.right);
+
+ // Get length of longest line
+ int width = 0;
+ for (int i = 0; i < lines.length; i++) {
+ width = Math.max(width, metrics.stringWidth(lines[i]));
+ }
+
+ width += insets.left + insets.right;
+
+ // Set preferred width
+ if (prefWidth < 0) {
+ prefWidth = width;
+ }
+
+ int height = lines.length * metrics.getHeight() + insets.top +
+ insets.bottom;
+
+ return new Dimension(width, height);
+ }
+
+ //
+ // JComponent methods
+ //
+
+ @Override
+ protected void paintComponent(Graphics g) {
+
+ // Turn on anti-aliasing if appropriate
+ GUIUtil.setAARendering((Graphics2D)g);
+
+ if (isOpaque()) {
+ g.setColor(getBackground());
+ g.fillRect(0, 0, getWidth(), getHeight());
+ }
+
+ Font font = getFont();
+ FontMetrics metrics = getFontMetrics(font);
+ int rowHeight = metrics.getHeight();
+
+ Insets insets = getInsets();
+ int y = insets.top + metrics.getAscent();
+ String[] lines = getLines(getWidth() - insets.left - insets.right);
+
+ g.setColor(getForeground());
+ g.setFont(font);
+
+ for (int i = 0; i < lines.length; i++) {
+ g.drawString(lines[i], insets.left, y);
+ y += rowHeight;
+ }
+ }
+
+ //
+ // WrappingText methods
+ //
+
+ /**
+ * Gets the initial preferred width of this {@code WrappingText}.
+ * Called by {@code getPreferredSize}. The width here is determined by
+ * the number of columns, the column width, and the insets of this
+ * {@code Component}.
+ *
+ * @return the preferred width in pixels, or -1 if
+ * {@code getPreferredSize} should determine it based on
+ * the longest line in the text
+ */
+ public int getPreferredWidth() {
+ return prefWidth;
+ }
+
+ public boolean getPreferredWidthIsStatic() {
+ return prefWidthIsStatic;
+ }
+
+ /**
+ * Sets the preferred width of this {@code WrappingText}, based on the given
+ * number of columns. This value ultimately determines the preferred
+ * height.
+ * <p>
+ * If not specified, the initial preferred width is determined by the length
+ * of the longest line of text.
+ *
+ * @param prefWidth
+ * the preferred width, in pixels
+ */
+ public void setPreferredWidth(int prefWidth) {
+ if (this.prefWidth != prefWidth && !prefWidthIsStatic) {
+ this.prefWidth = prefWidth;
+ revalidateLater();
+ }
+ }
+
+ /**
+ * Sets the preferred width of this {@code WrappingText}, based on the given
+ * number of columns. Column width is the width of the character 'm' in the
+ * current font.
+ *
+ * @param columns
+ * the preferred width, in columns
+ */
+ public void setPreferredWidthInColumns(int columns) {
+ int prefWidth = 0;
+
+ if (columns < 0) {
+ prefWidth = -1;
+ } else {
+ int columnWidth = getFontMetrics(getFont()).charWidth('m');
+ Insets insets = getInsets();
+ prefWidth = columns * columnWidth + insets.left + insets.right;
+ }
+
+ setPreferredWidth(prefWidth);
+ }
+
+ public String getText() {
+ return text;
+ }
+
+ public void setPreferredWidthIsStatic(boolean prefWidthIsStatic) {
+ this.prefWidthIsStatic = prefWidthIsStatic;
+ }
+
+ public void setText(String text) {
+ this.text = text;
+ revalidateLater();
+ repaint();
+ }
+
+ //
+ // Private methods
+ //
+
+ private String[] getLines(int width) {
+ ArrayList<String> lines = new ArrayList<String>();
+ FontMetrics metrics = getFontMetrics(getFont());
+ String text = getText();
+
+ // The current line
+ StringBuffer line = new StringBuffer();
+
+ // Space that will be added to current line as long as it's not the last
+ // token in the line
+ String pendingSpace = "";
+ int pendingSpaceWidth = 0;
+
+ String[] groups;
+ int x = 0;
+ while ((groups = TextUtil.match(text,
+ "^((\\r?\\n)|(\\s+)|(\\S+))((?s).*)")) != null) {
+
+ String newline = groups[2];
+ String space = groups[3];
+ String word = groups[4];
+ text = groups[5];
+
+ if (newline != null) {
+ lines.add(line.toString());
+ line.setLength(0);
+ pendingSpace = "";
+ pendingSpaceWidth = 0;
+ x = 0;
+ } else
+
+ if (space != null) {
+ pendingSpace = space;
+ pendingSpaceWidth = metrics.stringWidth(pendingSpace);
+ } else
+
+ if (word != null) {
+ int wordWidth = metrics.stringWidth(word);
+ int newX = x + pendingSpaceWidth + wordWidth;
+
+ // Will the pending space and this word fit on this line?
+ if (newX <= width) {
+ line.append(pendingSpace).append(word);
+ x = newX;
+ } else {
+ if (line.length() != 0) {
+ lines.add(line.toString());
+ line.setLength(0);
+ }
+
+ // Does this word fit on a line of its own?
+ if (wordWidth <= width) {
+ line.append(word);
+ x = wordWidth;
+ } else {
+ // Split word
+ char[] chars = word.toCharArray();
+ x = 0;
+ for (int i = 0; i < chars.length; i++) {
+ char c = chars[i];
+ int charWidth = metrics.charWidth(c);
+ x += charWidth;
+
+ // Has this line exceeded the maximum width?
+ if (x > width && line.length() != 0) {
+ lines.add(line.toString());
+ line.setLength(0);
+ x = charWidth;
+ }
+
+ line.append(c);
+ }
+ }
+ }
+
+ pendingSpace = "";
+ pendingSpaceWidth = 0;
+ }
+ }
+
+ if (line.length() > 0) {
+ lines.add(line.toString());
+ }
+
+ return lines.toArray(new String[lines.size()]);
+ }
+
+ private void revalidateLater() {
+ SwingUtilities.invokeLater(
+ new Runnable() {
+ @Override
+ public void run() {
+ revalidate();
+ }
+ });
+ }
+}