From 6592ae110a5e550246f8ffe4e17d7d3dbb719c35 Mon Sep 17 00:00:00 2001 From: dev Date: Sat, 16 Jul 2011 08:31:13 +0000 Subject: [PATCH] Imported MultiLineLabel, Swing Component. Added static general information labels to the overview tab. --- src/net/i2p/itoopie/Main.java | 1 + src/net/i2p/itoopie/gui/OverviewTab.java | 104 ++- .../itoopie/gui/component/MultiLineLabel.java | 124 ++++ .../gui/component/multilinelabel/Effects.java | 345 ++++++++++ .../multilinelabel/MultiLineLabelUI.java | 608 ++++++++++++++++++ .../multilinelabel/MultiLineShadowUI.java | 91 +++ .../multilinelabel/ShadowLabelUI.java | 71 ++ 7 files changed, 1337 insertions(+), 7 deletions(-) create mode 100644 src/net/i2p/itoopie/gui/component/MultiLineLabel.java create mode 100644 src/net/i2p/itoopie/gui/component/multilinelabel/Effects.java create mode 100644 src/net/i2p/itoopie/gui/component/multilinelabel/MultiLineLabelUI.java create mode 100644 src/net/i2p/itoopie/gui/component/multilinelabel/MultiLineShadowUI.java create mode 100644 src/net/i2p/itoopie/gui/component/multilinelabel/ShadowLabelUI.java diff --git a/src/net/i2p/itoopie/Main.java b/src/net/i2p/itoopie/Main.java index 969e2357d..e08fd5d8b 100644 --- a/src/net/i2p/itoopie/Main.java +++ b/src/net/i2p/itoopie/Main.java @@ -265,6 +265,7 @@ public class Main { }*/ /* + // Test shutdown - worked at one point :) Possibly now as well. System.out.println("\nSetRouterRunner: Shutdown "); try { SetRouterRunner.execute(ROUTER_RUNNER.SHUTDOWN); diff --git a/src/net/i2p/itoopie/gui/OverviewTab.java b/src/net/i2p/itoopie/gui/OverviewTab.java index 131461616..1b9034499 100644 --- a/src/net/i2p/itoopie/gui/OverviewTab.java +++ b/src/net/i2p/itoopie/gui/OverviewTab.java @@ -1,23 +1,39 @@ package net.i2p.itoopie.gui; import java.awt.Color; +import java.awt.EventQueue; import info.monitorenter.gui.chart.Chart2D; import info.monitorenter.gui.chart.views.ChartPanel; import javax.swing.BorderFactory; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.SwingConstants; import javax.swing.event.ChangeEvent; import net.i2p.itoopie.gui.component.BandwidthChart; +import net.i2p.itoopie.gui.component.MultiLineLabel; import net.i2p.itoopie.gui.component.ParticipatingTunnelsChart; import net.i2p.itoopie.gui.component.TabLogoPanel; +import net.i2p.itoopie.gui.component.multilinelabel.MultiLineLabelUI; +import net.i2p.itoopie.i18n.Transl; public class OverviewTab extends TabLogoPanel { + JLabel lblI2P; + JLabel lblVersion; + JLabel lblVersionSpecified; + JLabel lblStatus; + JLabel lblStatusSpecified; + JLabel lblUptime; + JLabel lblUptimeSpecified; + JLabel lblNetworkStatus; + MultiLineLabel lblNetworkStatusSpecified; public OverviewTab(String imageName) { super(imageName); super.setLayout(null); - + Chart2D bwChart = BandwidthChart.getChart(); Chart2D partTunnelChart = ParticipatingTunnelsChart.getChart(); ChartPanel pt = new ChartPanel(partTunnelChart); @@ -25,20 +41,94 @@ public class OverviewTab extends TabLogoPanel { pt.setLocation(15, 10); pt.setBorder(BorderFactory.createLineBorder(Color.GRAY)); ChartPanel cp = new ChartPanel(bwChart); - cp.setSize(300,135); + cp.setSize(300, 135); cp.setLocation(15, 155); cp.setBorder(BorderFactory.createLineBorder(Color.GRAY)); - + add(pt); add(cp); + + lblI2P = new JLabel(); + add(lblI2P); + lblI2P.setBounds(285, 30, 100, 15); + lblI2P.setText("I2P"); + lblI2P.setHorizontalAlignment(SwingConstants.RIGHT); + + lblVersion = new JLabel(); + add(lblVersion); + lblVersion.setBounds(285, 50, 100, 15); + lblVersion.setText(Transl._("Version:")); + lblVersion.setHorizontalAlignment(SwingConstants.RIGHT); + + lblVersionSpecified = new JLabel(); + add(lblVersionSpecified); + lblVersionSpecified.setBounds(395, 50, 140, 15); + lblVersionSpecified.setHorizontalAlignment(SwingConstants.LEFT); + lblVersionSpecified.setText("0.8.7-48rc"); // Delete Me + + + lblUptime = new JLabel(); + add(lblUptime); + lblUptime.setBounds(285, 70, 100, 15); + lblUptime.setHorizontalAlignment(SwingConstants.RIGHT); + lblUptime.setText(Transl._("Uptime:")); + + lblUptimeSpecified = new JLabel(); + add(lblUptimeSpecified); + lblUptimeSpecified.setBounds(395, 70, 140, 15); + lblUptimeSpecified.setHorizontalAlignment(SwingConstants.LEFT); + lblUptimeSpecified.setText("93 min"); // Delete Me + + + lblStatus = new JLabel(); + add(lblStatus); + lblStatus.setBounds(285, 90, 100, 15); + lblStatus.setHorizontalAlignment(SwingConstants.RIGHT); + lblStatus.setText(Transl._("Status:")); + + lblStatusSpecified = new JLabel(); + add(lblStatusSpecified); + lblStatusSpecified.setBounds(395, 90, 140, 15); + lblStatusSpecified.setHorizontalAlignment(SwingConstants.LEFT); + lblStatusSpecified.setText("Rejecting Tunnels"); // Delete Me + + lblNetworkStatus = new JLabel(); + add(lblNetworkStatus); + lblNetworkStatus.setBounds(285, 110, 100, 15); + lblNetworkStatus.setHorizontalAlignment(SwingConstants.RIGHT); + lblNetworkStatus.setText(Transl._("Netstatus:")); + + lblNetworkStatusSpecified = new MultiLineLabel(); + add(lblNetworkStatusSpecified); + lblNetworkStatusSpecified.setBounds(395, 110, 130, 60); + lblNetworkStatusSpecified.setHorizontalAlignment(SwingConstants.LEFT); + lblNetworkStatusSpecified.setVerticalTextAlignment(JLabel.TOP); + lblNetworkStatusSpecified.setText("WARN-Firewalled with Inbound TCP Enabled".replace('-', ' ')); // Delete Me + + validate(); } - - @Override public void onTabFocus(ChangeEvent e) { - System.out.println("OverviewTab onTabFocus()"); - + // Do thigns when shown? } + /** + * Launch the application. + */ + public static void main(String[] args) { + EventQueue.invokeLater(new Runnable() { + public void run() { + try { + JFrame frame = new JFrame(); + frame.setBounds(0, 0, Main.FRAME_WIDTH, Main.FRAME_HEIGHT); + OverviewTab window = new OverviewTab("itoopie-opaque12"); + frame.add(window); + frame.setVisible(true); + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + } } diff --git a/src/net/i2p/itoopie/gui/component/MultiLineLabel.java b/src/net/i2p/itoopie/gui/component/MultiLineLabel.java new file mode 100644 index 000000000..68fc29c8d --- /dev/null +++ b/src/net/i2p/itoopie/gui/component/MultiLineLabel.java @@ -0,0 +1,124 @@ +/* + * The MIT License + * + * Copyright (c) 2009 Samuel Sjoberg + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package net.i2p.itoopie.gui.component; + +import java.awt.Rectangle; + +import javax.swing.JLabel; + +import net.i2p.itoopie.gui.component.multilinelabel.MultiLineLabelUI; + + + +/** + * A {@link JLabel} with support for multi-line text that wraps when the line + * doesn't fit in the available width. Multi-line text support is handled by the + * {@link MultiLineLabelUI}, the default UI delegate of this component. The text + * in the label can be horizontally and vertically aligned, relative to the + * bounds of the component. + * + * @author Samuel Sjoberg, http://samuelsjoberg.com + * @version 1.0.0 + */ +public class MultiLineLabel extends JLabel { + + /** Default serial version UID. */ + private static final long serialVersionUID = 1L; + + /** Horizontal text alignment. */ + private int halign = LEFT; + + /** Vertical text alignment. */ + private int valign = CENTER; + + /** Cache to save heap allocations. */ + private Rectangle bounds; + + /** + * Creates a new empty label. + */ + public MultiLineLabel() { + super(); + setUI(MultiLineLabelUI.labelUI); + } + + /** + * Creates a new label with text value. + * + * @param text + * the value of the label + */ + public MultiLineLabel(String text) { + this(); + setText(text); + } + + /** {@inheritDoc} */ + public Rectangle getBounds() { + if (bounds == null) { + bounds = new Rectangle(); + } + return super.getBounds(bounds); + } + + /** + * Set the vertical text alignment. + * + * @param alignment + * vertical alignment + */ + public void setVerticalTextAlignment(int alignment) { + firePropertyChange("verticalTextAlignment", valign, alignment); + valign = alignment; + } + + /** + * Set the horizontal text alignment. + * + * @param alignment + * horizontal alignment + */ + public void setHorizontalTextAlignment(int alignment) { + firePropertyChange("horizontalTextAlignment", halign, alignment); + halign = alignment; + } + + /** + * Get the vertical text alignment. + * + * @return vertical text alignment + */ + public int getVerticalTextAlignment() { + return valign; + } + + /** + * Get the horizontal text alignment. + * + * @return horizontal text alignment + */ + public int getHorizontalTextAlignment() { + return halign; + } +} diff --git a/src/net/i2p/itoopie/gui/component/multilinelabel/Effects.java b/src/net/i2p/itoopie/gui/component/multilinelabel/Effects.java new file mode 100644 index 000000000..15b2d1f91 --- /dev/null +++ b/src/net/i2p/itoopie/gui/component/multilinelabel/Effects.java @@ -0,0 +1,345 @@ +package net.i2p.itoopie.gui.component.multilinelabel; + +/* + * The MIT License + * + * Copyright (c) 2009 Samuel Sjoberg + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import java.awt.AlphaComposite; +import java.awt.Color; +import java.awt.Composite; +import java.awt.GradientPaint; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.RenderingHints; +import java.awt.event.ComponentAdapter; +import java.awt.event.ComponentEvent; + +import javax.swing.JComponent; + +/** + * Static graphics and look and feel effects. + * + * @author Samuel Sjoberg, http://samuelsjoberg.com + * @version 1.3.0 + */ +public final class Effects { + + /** Prevent initialization. */ + private Effects() { + } + + /** Text drop shadow size. */ + public static final int TEXT_SHADOW_SIZE = 2; + + /** + * Set the color to black with given alpha value. + * + * @param g + * the graphics to paint on + * @param alpha + * the alpha value between 0-1. + */ + public static void setAlpha(Graphics g, float alpha) { + g.setColor(new Color(0, 0, 0, Math.round(255 * alpha))); + } + + /** + * Remove the alpha channel from the passed color. + * + * @param c + * a color + * @return the same color, but without an alpha channel + */ + public static Color removeAlpha(Color c) { + if (c.getAlpha() != 100) { + c = new Color(c.getRGB()); + } + return c; + } + + /** + * Draw a string with a blur or shadow effect. The light angle is assumed to + * be 0 degrees, (i.e., window is illuminated from top). The effect is + * intended to be subtle to be usable in as many text components as + * possible. The effect is generated with multiple calls to draw string. + * This method paints the text on coordinates tx, + * ty. If text should be painted elsewhere, a transform should + * be applied to the graphics before passing it. + *

+ * All modifications to the graphics object is restored by this method + * before returning. + *

+ * Based on code by Romain Guy, {@linkplain http://filthyrichclients.org/}, + * see examples from chapter 16. + * + * @param g + * graphics component to paint on + * @param s + * the string to paint + * @param c + * effect color + * @param size + * effect size + * @param tx + * x-coordinate translation (i.e, pixels to move the center of + * the x-axis) + * @param ty + * y-coordinate translation (i.e, pixels to move the center of + * the y-axis) + * @param isShadow + * true if this is a shadow being painted that + * should be slightly offset to look more like a shadow being + * casted, otherwise false. + */ + private static void paintTextEffect(Graphics2D g, String s, Color c, + int size, double tx, double ty, boolean isShadow) { + + prepareGraphics(g); + + final float opacity = 0.8f; // Effect "darkness". + final Composite oldComposite = g.getComposite(); + final Color oldColor = g.getColor(); + + // Use a alpha blend smaller than 1 to prevent the effect from becoming + // too dark when multiple paints occur on top of each other. + float preAlpha = 0.4f; + if (oldComposite instanceof AlphaComposite + && ((AlphaComposite) oldComposite).getRule() == AlphaComposite.SRC_OVER) { + preAlpha = Math.min(((AlphaComposite) oldComposite).getAlpha(), + preAlpha); + } + g.setColor(c); + + g.translate(tx, ty); + + // If the effect is a shadow it looks better to stop painting a bit to + // early... (shadow will look softer). + int maxSize = isShadow ? size - 1 : size; + + for (int i = -size; i <= maxSize; i++) { + for (int j = -size; j <= maxSize; j++) { + double distance = i * i + j * j; + float alpha = opacity; + if (distance > 0.0d) { + alpha = (float) (1.0f / ((distance * size) * opacity)); + } + alpha *= preAlpha; + if (alpha > 1.0f) { + alpha = 1.0f; + } + g.setComposite(AlphaComposite.getInstance( + AlphaComposite.SRC_OVER, alpha)); + g.drawString(s, i + size, j + size); + } + } + + // Restore graphics + g.translate(-tx, -ty); + g.setComposite(oldComposite); + g.setColor(oldColor); + + g.drawString(s, 0, 0); + } + + private static void prepareGraphics(Graphics2D g) { + g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, + RenderingHints.VALUE_TEXT_ANTIALIAS_ON); + } + + /** + * Draw a string with a drop shadow. The light angle is assumed to be 0 + * degrees, (i.e., window is illuminated from top) and the shadow size is 2, + * with a 1 pixel vertical displacement. The shadow is intended to be subtle + * to be usable in as many text components as possible. The shadow is + * generated with multiple calls to draw string. This method paints the text + * on coordinates 0, 1. If text should be painted elsewhere, a transform + * should be applied to the graphics before passing it. + *

+ * All modifications to the graphics object is restored by this method + * before returning. + * + * @see #paintTextEffect(Graphics2D, String, Color, int, boolean) + * + * @param g + * graphics component to paint on + * @param s + * the string to paint + * @param c + * the color of the shadow. Any alpha channel will be discarded + */ + public static void paintTextShadow(Graphics2D g, String s, Color c) { + paintTextEffect(g, s, removeAlpha(c), TEXT_SHADOW_SIZE, + -TEXT_SHADOW_SIZE, 1 - TEXT_SHADOW_SIZE, true); + } + + /** + * Draw a string with a drop shadow. The light angle is assumed to be 0 + * degrees, (i.e., window is illuminated from top) and the shadow size is 2, + * with a 1 pixel vertical displacement. The shadow is intended to be subtle + * to be usable in as many text components as possible. The shadow is + * generated with multiple calls to draw string. This method paints the text + * on coordinates 0, 1. If text should be painted elsewhere, a transform + * should be applied to the graphics before passing it. + *

+ * All modifications to the graphics object is restored by this method + * before returning. + * + * @see #paintTextEffect(Graphics2D, String, Color, int, boolean) + * + * @param g + * graphics component to paint on + * @param s + * the string to paint + */ + public static void paintTextShadow(Graphics2D g, String s) { + paintTextShadow(g, s, Color.BLACK); + } + + /** + * Draw a string with a glow effect. Glow differs from a drop shadow in that + * it isn't offset in any direction (i.e., not affected by + * "lighting conditions"). + *

+ * All modifications to the graphics object is restored by this method + * before returning. + * + * @param g + * graphics component to paint on + * @param s + * the string to draw + * @param glow + * the solid glow color. Do not use the alpha channel as this + * will be discarded + */ + public static void paintTextGlow(Graphics2D g, String s, Color glow) { + paintTextEffect(g, s, removeAlpha(glow), TEXT_SHADOW_SIZE, + -TEXT_SHADOW_SIZE, -TEXT_SHADOW_SIZE, false); + } + + /** + * Utility to assist with painting of a vertical gradient. The opaque + * property of the component being painted is not honored. If + * {@link #paint(Graphics, JComponent)} is invoked a gradient background is + * always painted. + *

+ * A note on cyclic gradients:
+ * Cyclic gradients yields better performance, however they cannot be safely + * used to paint backgrounds on components that perform partial repaints + * (e.g., JList). Cyclic gradients can be turned on using + * {@link #setCyclic(boolean)} but are not used by default in this painter. + * + * @author Samuel Sjoberg, Extenda AB + */ + public static class GradientPainter { + + /** Start color. */ + private Color c1; + + /** End color. */ + private Color c2; + + /** Cached gradient paint. */ + private GradientPaint paint; + + /** Cyclic gradient. */ + private boolean cyclic = false; + + /** + * Create a new GradientPainter using the background color + * of the passed component. The gradient will fade from the background + * color in to a darker shade of the same color. + * + * @param c + * the component owning the painter + */ + public GradientPainter(JComponent c) { + this(c, c.getBackground(), c.getBackground().darker().darker()); + } + + /** + * Create a new GradientPainter using the passed colors. + * The gradient will fade from c1 to c2. + * + * @param c + * the component owning the painter + * @param c1 + * the start color + * @param c2 + * the end color + */ + public GradientPainter(JComponent c, Color c1, Color c2) { + this.c1 = c1; + this.c2 = c2; + c.addComponentListener(new ComponentAdapter() { + public void componentResized(ComponentEvent e) { + paint = null; + } + }); + } + + /** + * Set the gradient colors. For changes to take effect, the component + * must be repainted by invoking e.g. {@link JComponent#repaint()}. + * + * @param c1 + * the start color + * @param c2 + * the end color + */ + public void setColors(Color c1, Color c2) { + this.c1 = c1; + this.c2 = c2; + paint = null; + } + + /** + * Use cyclic gradients. Default behavior is non-cyclic gradients. + * + * @param cyclic + * If true, cyclic gradients a used, otherwise + * non-cyclic gradients are used. + */ + public void setCyclic(boolean cyclic) { + this.cyclic = cyclic; + paint = null; + } + + /** + * Fill the component with a gradient background. This method does not + * honor the opaque property. + * + * @param g + * graphics to paint upon + * @param c + * the component being painted + */ + public void paint(Graphics g, JComponent c) { + if (paint == null) { + paint = new GradientPaint(0, 0, c1, 0, c.getHeight(), c2, + cyclic); + } + ((Graphics2D) g).setPaint(paint); + g.fillRect(0, 0, c.getWidth(), c.getHeight()); + } + } +} diff --git a/src/net/i2p/itoopie/gui/component/multilinelabel/MultiLineLabelUI.java b/src/net/i2p/itoopie/gui/component/multilinelabel/MultiLineLabelUI.java new file mode 100644 index 000000000..eeb51acf8 --- /dev/null +++ b/src/net/i2p/itoopie/gui/component/multilinelabel/MultiLineLabelUI.java @@ -0,0 +1,608 @@ +package net.i2p.itoopie.gui.component.multilinelabel; + +/* + * The MIT License + * + * Copyright (c) 2009 Samuel Sjoberg + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import java.awt.Dimension; +import java.awt.FontMetrics; +import java.awt.Graphics; +import java.awt.Insets; +import java.awt.Rectangle; +import java.awt.event.ComponentEvent; +import java.awt.event.ComponentListener; +import java.beans.PropertyChangeEvent; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import javax.swing.Icon; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.plaf.ComponentUI; +import javax.swing.plaf.LabelUI; +import javax.swing.plaf.basic.BasicLabelUI; +import javax.swing.text.BadLocationException; +import javax.swing.text.Document; +import javax.swing.text.Element; +import javax.swing.text.PlainDocument; +import javax.swing.text.Segment; +import javax.swing.text.Utilities; +import javax.swing.text.View; + +import net.i2p.itoopie.gui.component.MultiLineLabel; + + +/** + * Label UI delegate that supports multiple lines and line wrapping. Hard line + * breaks (\n) are preserved. If the dimensions of the label is too + * small to fit all content, the string will be clipped and "..." appended to + * the end of the visible text (similar to the default behavior of + * JLabel). If used in conjunction with a {@link MultiLineLabel}, + * text alignment (horizontal and vertical) is supported. The UI delegate can be + * used on a regular JLabel if text alignment isn't required. The + * default alignment, left and vertically centered, will then be used. + *

+ * Example of usage: + * + *

+ * JLabel myLabel = new JLabel();
+ * myLabel.setUI(MultiLineLabelUI.labelUI);
+ * myLabel.setText("A long label that will wrap automatically.");
+ * 
+ * + *

+ * The line and wrapping support is implemented without using a + * View to make it easy for subclasses to add custom text effects + * by overriding {@link #paintEnabledText(JLabel, Graphics, String, int, int)} + * and {@link #paintDisabledText(JLabel, Graphics, String, int, int)}. This + * class is designed to be easily extended by subclasses. + * + * @author Samuel Sjoberg, http://samuelsjoberg.com + * @version 1.3.0 + */ +public class MultiLineLabelUI extends BasicLabelUI implements ComponentListener { + + /** Shared instance of the UI delegate. */ + public static LabelUI labelUI = new MultiLineLabelUI(); + + /** + * Client property key used to store the calculated wrapped lines on the + * JLabel. + */ + public static final String PROPERTY_KEY = "WrappedText"; + + // Static references to avoid heap allocations. + protected static Rectangle paintIconR = new Rectangle(); + protected static Rectangle paintTextR = new Rectangle(); + protected static Rectangle paintViewR = new Rectangle(); + protected static Insets paintViewInsets = new Insets(0, 0, 0, 0); + + /** Font metrics of the JLabel being rendered. */ + protected FontMetrics metrics; + + /** Default size of the lines list. */ + protected static int defaultSize = 4; + + /** + * Get the shared UI instance. + * + * @param c + * the component about to be installed + * @return the shared UI delegate instance + */ + public static ComponentUI createUI(JComponent c) { + return labelUI; + } + + /** {@inheritDoc} */ + protected void uninstallDefaults(JLabel c) { + super.uninstallDefaults(c); + clearCache(c); + } + + /** {@inheritDoc} */ + protected void installListeners(JLabel c) { + super.installListeners(c); + c.addComponentListener(this); + } + + /** {@inheritDoc} */ + protected void uninstallListeners(JLabel c) { + super.uninstallListeners(c); + c.removeComponentListener(this); + } + + /** + * Clear the wrapped line cache. + * + * @param l + * the label containing a cached value + */ + protected void clearCache(JLabel l) { + l.putClientProperty(PROPERTY_KEY, null); + } + + /** {@inheritDoc} */ + public void propertyChange(PropertyChangeEvent e) { + super.propertyChange(e); + final String name = e.getPropertyName(); + if (name.equals("text") || "font".equals(name)) { + clearCache((JLabel) e.getSource()); + } + } + + /** + * Calculate the paint rectangles for the icon and text for the passed + * label. + * + * @param l + * a label + * @param fm + * the font metrics to use, or null to get the font + * metrics from the label + * @param width + * label width + * @param height + * label height + */ + protected void updateLayout(JLabel l, FontMetrics fm, int width, int height) { + if (fm == null) { + fm = l.getFontMetrics(l.getFont()); + } + metrics = fm; + + String text = l.getText(); + Icon icon = l.getIcon(); + Insets insets = l.getInsets(paintViewInsets); + + paintViewR.x = insets.left; + paintViewR.y = insets.top; + paintViewR.width = width - (insets.left + insets.right); + paintViewR.height = height - (insets.top + insets.bottom); + + paintIconR.x = paintIconR.y = paintIconR.width = paintIconR.height = 0; + paintTextR.x = paintTextR.y = paintTextR.width = paintTextR.height = 0; + + layoutCL(l, fm, text, icon, paintViewR, paintIconR, paintTextR); + } + + protected void prepareGraphics(Graphics g) { + } + + /** {@inheritDoc} */ + public void paint(Graphics g, JComponent c) { + + // parent's update method fills the background + prepareGraphics(g); + + JLabel label = (JLabel) c; + String text = label.getText(); + Icon icon = (label.isEnabled()) ? label.getIcon() : label + .getDisabledIcon(); + + if ((icon == null) && (text == null)) { + return; + } + + FontMetrics fm = g.getFontMetrics(); + + updateLayout(label, fm, c.getWidth(), c.getHeight()); + + if (icon != null) { + icon.paintIcon(c, g, paintIconR.x, paintIconR.y); + } + + if (text != null) { + View v = (View) c.getClientProperty("html"); + if (v != null) { + // HTML view disables multi-line painting. + v.paint(g, paintTextR); + } else { + // Paint the multi line text + paintTextLines(g, label, fm); + } + } + } + + /** + * Paint the wrapped text lines. + * + * @param g + * graphics component to paint on + * @param label + * the label being painted + * @param fm + * font metrics for current font + */ + protected void paintTextLines(Graphics g, JLabel label, FontMetrics fm) { + List lines = getTextLines(label); + + // Available component height to paint on. + int height = getAvailableHeight(label); + + int textHeight = lines.size() * fm.getHeight(); + while (textHeight > height) { + // Remove one line until no. of visible lines is found. + textHeight -= fm.getHeight(); + } + paintTextR.height = Math.min(textHeight, height); + paintTextR.y = alignmentY(label, fm, paintTextR); + + int textX = paintTextR.x; + int textY = paintTextR.y; + + for (Iterator it = lines.iterator(); it.hasNext() + && paintTextR.contains(textX, textY + getAscent(fm)); textY += fm + .getHeight()) { + + String text = it.next().trim(); + + if (it.hasNext() + && !paintTextR.contains(textX, textY + fm.getHeight() + + getAscent(fm))) { + // The last visible row, add a clip indication. + text = clip(text, fm, paintTextR); + } + + int x = alignmentX(label, fm, text, paintTextR); + + if (label.isEnabled()) { + paintEnabledText(label, g, text, x, textY); + } else { + paintDisabledText(label, g, text, x, textY); + } + } + } + + /** + * Returns the available height to paint text on. This is the height of the + * passed component with insets subtracted. + * + * @param l + * a component + * @return the available height + */ + protected int getAvailableHeight(JLabel l) { + l.getInsets(paintViewInsets); + return l.getHeight() - paintViewInsets.top - paintViewInsets.bottom; + } + + /** + * Add a clip indication to the string. It is important that the string + * length does not exceed the length or the original string. + * + * @param text + * the to be painted + * @param fm + * font metrics + * @param bounds + * the text bounds + * @return the clipped string + */ + protected String clip(String text, FontMetrics fm, Rectangle bounds) { + // Fast and lazy way to insert a clip indication is to simply replace + // the last characters in the string with the clip indication. + // A better way would be to use metrics and calculate how many (if any) + // characters that need to be replaced. + if (text.length() < 3) { + return "..."; + } + return text.substring(0, text.length() - 3) + "..."; + } + + /** + * Establish the vertical text alignment. The default alignment is to center + * the text in the label. + * + * @param label + * the label to paint + * @param fm + * font metrics + * @param bounds + * the text bounds rectangle + * @return the vertical text alignment, defaults to CENTER. + */ + protected int alignmentY(JLabel label, FontMetrics fm, Rectangle bounds) { + final int height = getAvailableHeight(label); + int textHeight = bounds.height; + + if (label instanceof MultiLineLabel) { + int align = ((MultiLineLabel) label).getVerticalTextAlignment(); + switch (align) { + case JLabel.TOP: + return getAscent(fm) + paintViewInsets.top; + case JLabel.BOTTOM: + return getAscent(fm) + height - paintViewInsets.top + + paintViewInsets.bottom - textHeight; + default: + } + } + + // Center alignment + int textY = paintViewInsets.top + (height - textHeight) / 2 + + getAscent(fm); + return Math.max(textY, getAscent(fm) + paintViewInsets.top); + } + + private static int getAscent(FontMetrics fm) { + return fm.getAscent() + fm.getLeading(); + } + + /** + * Establish the horizontal text alignment. The default alignment is left + * aligned text. + * + * @param label + * the label to paint + * @param fm + * font metrics + * @param s + * the string to paint + * @param bounds + * the text bounds rectangle + * @return the x-coordinate to use when painting for proper alignment + */ + protected int alignmentX(JLabel label, FontMetrics fm, String s, + Rectangle bounds) { + if (label instanceof MultiLineLabel) { + int align = ((MultiLineLabel) label).getHorizontalTextAlignment(); + switch (align) { + case JLabel.RIGHT: + return bounds.x + paintViewR.width - fm.stringWidth(s); + case JLabel.CENTER: + return bounds.x + paintViewR.width / 2 - fm.stringWidth(s) / 2; + default: + return bounds.x; + } + } + return bounds.x; + } + + /** + * Check the given string to see if it should be rendered as HTML. Code + * based on implementation found in + * BasicHTML.isHTMLString(String) in future JDKs. + * + * @param s + * the string + * @return true if string is HTML, otherwise false + */ + private static boolean isHTMLString(String s) { + if (s != null) { + if ((s.length() >= 6) && (s.charAt(0) == '<') + && (s.charAt(5) == '>')) { + String tag = s.substring(1, 5); + return tag.equalsIgnoreCase("html"); + } + } + return false; + } + + /** {@inheritDoc} */ + public Dimension getPreferredSize(JComponent c) { + Dimension d = super.getPreferredSize(c); + JLabel label = (JLabel) c; + + if (isHTMLString(label.getText())) { + return d; // HTML overrides everything and we don't need to process + } + + // Width calculated by super is OK. The preferred width is the width of + // the unwrapped content as long as it does not exceed the width of the + // parent container. + + if (c.getParent() != null) { + // Ensure that preferred width never exceeds the available width + // (including its border insets) of the parent container. + Insets insets = c.getParent().getInsets(); + Dimension size = c.getParent().getSize(); + if (size.width > 0) { + // If width isn't set component shouldn't adjust. + d.width = size.width - insets.left - insets.right; + } + } + + updateLayout(label, null, d.width, d.height); + + // The preferred height is either the preferred height of the text + // lines, or the height of the icon. + d.height = Math.max(d.height, getPreferredHeight(label)); + + return d; + } + + /** + * The preferred height of the label is the height of the lines with added + * top and bottom insets. + * + * @param label + * the label + * @return the preferred height of the wrapped lines. + */ + protected int getPreferredHeight(JLabel label) { + int numOfLines = getTextLines(label).size(); + Insets insets = label.getInsets(paintViewInsets); + return numOfLines * metrics.getHeight() + insets.top + insets.bottom; + } + + /** + * Get the lines of text contained in the text label. The prepared lines is + * cached as a client property, accessible via {@link #PROPERTY_KEY}. + * + * @param l + * the label + * @return the text lines of the label. + */ + @SuppressWarnings("unchecked") + protected List getTextLines(JLabel l) { + List lines = (List) l.getClientProperty(PROPERTY_KEY); + if (lines == null) { + lines = prepareLines(l); + l.putClientProperty(PROPERTY_KEY, lines); + } + return lines; + } + + /** {@inheritDoc} */ + public void componentHidden(ComponentEvent e) { + // Don't care + } + + /** {@inheritDoc} */ + public void componentMoved(ComponentEvent e) { + // Don't care + } + + /** {@inheritDoc} */ + public void componentResized(ComponentEvent e) { + clearCache((JLabel) e.getSource()); + } + + /** {@inheritDoc} */ + public void componentShown(ComponentEvent e) { + // Don't care + } + + /** + * Prepare the text lines for rendering. The lines are wrapped to fit in the + * current available space for text. Explicit line breaks are preserved. + * + * @param l + * the label to render + * @return a list of text lines to render + */ + protected List prepareLines(JLabel l) { + List lines = new ArrayList(defaultSize); + String text = l.getText(); + if (text == null) { + return null; // Null guard + } + PlainDocument doc = new PlainDocument(); + try { + doc.insertString(0, text, null); + } catch (BadLocationException e) { + return null; + } + Element root = doc.getDefaultRootElement(); + for (int i = 0, j = root.getElementCount(); i < j; i++) { + wrap(lines, root.getElement(i)); + } + return lines; + } + + /** + * If necessary, wrap the text into multiple lines. + * + * @param lines + * line array in which to store the wrapped lines + * @param elem + * the document element containing the text content + */ + protected void wrap(List lines, Element elem) { + int p1 = elem.getEndOffset(); + Document doc = elem.getDocument(); + for (int p0 = elem.getStartOffset(); p0 < p1;) { + int p = calculateBreakPosition(doc, p0, p1); + try { + lines.add(doc.getText(p0, p - p0)); + } catch (BadLocationException e) { + throw new Error("Can't get line text. p0=" + p0 + " p=" + p); + } + p0 = (p == p0) ? p1 : p; + } + } + + /** + * Calculate the position on which to break (wrap) the line. + * + * @param doc + * the document + * @param p0 + * start position + * @param p1 + * end position + * @return the actual end position, will be p1 if content does + * not need to wrap, otherwise it will be less than p1. + */ + protected int calculateBreakPosition(Document doc, int p0, int p1) { + Segment segment = SegmentCache.getSegment(); + try { + doc.getText(p0, p1 - p0, segment); + } catch (BadLocationException e) { + throw new Error("Can't get line text"); + } + + int width = paintTextR.width; + int p = p0 + + Utilities.getBreakLocation(segment, metrics, 0, width, null, + p0); + SegmentCache.releaseSegment(segment); + return p; + } + + /** + * Static singleton {@link Segment} cache. + * + * @see javax.swing.text.SegmentCache + * + * @author Samuel Sjoberg + */ + protected static final class SegmentCache { + + /** Reused segments. */ + private ArrayList segments = new ArrayList(2); + + /** Singleton instance. */ + private static SegmentCache cache = new SegmentCache(); + + /** Private constructor. */ + private SegmentCache() { + } + + /** + * Returns a Segment. When done, the Segment + * should be recycled by invoking {@link #releaseSegment(Segment)}. + * + * @return a Segment. + */ + public static Segment getSegment() { + int size = cache.segments.size(); + if (size > 0) { + return cache.segments.remove(size - 1); + } + return new Segment(); + } + + /** + * Releases a Segment. A segment should not be used after + * it is released, and a segment should never be released more than + * once. + */ + public static void releaseSegment(Segment segment) { + segment.array = null; + segment.count = 0; + cache.segments.add(segment); + } + } +} \ No newline at end of file diff --git a/src/net/i2p/itoopie/gui/component/multilinelabel/MultiLineShadowUI.java b/src/net/i2p/itoopie/gui/component/multilinelabel/MultiLineShadowUI.java new file mode 100644 index 000000000..59b3e15e1 --- /dev/null +++ b/src/net/i2p/itoopie/gui/component/multilinelabel/MultiLineShadowUI.java @@ -0,0 +1,91 @@ +package net.i2p.itoopie.gui.component.multilinelabel; + +/* + * The MIT License + * + * Copyright (c) 2009 Samuel Sjoberg + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import java.awt.Graphics; +import java.awt.Graphics2D; + +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.plaf.ComponentUI; +import javax.swing.plaf.LabelUI; + +/** + * MultiLineLabelUI that paints the text with a drop shadow. + * + * @see MultiLineLabelUI + * @see Effects#paintTextShadow(Graphics2D, String) + * + * @author Samuel Sjoberg, http://samuelsjoberg.com + * @version 1.0.0 + */ +public class MultiLineShadowUI extends MultiLineLabelUI { + + /** Shared UI instance. */ + public static LabelUI labelUI = new MultiLineShadowUI(); + + /** + * Get the shared UI instance. + * + * @param c + * the component requesting a UI delegate + * @return the shared UI instance. + */ + public static ComponentUI createUI(JComponent c) { + return labelUI; + } + + /** + * Paint the text with a text effect. + * + * @param g + * graphics component used to paint on + * @param s + * the string to paint + * @param textX + * the x coordinate + * @param textY + * the y coordinate + */ + private void paintText(Graphics g, String s, int textX, int textY) { + g.translate(textX, textY); + Effects.paintTextShadow((Graphics2D) g, s); + g.translate(-textX, -textY); + } + + /** {@inheritDoc} */ + protected void paintEnabledText(JLabel l, Graphics g, String s, int textX, + int textY) { + g.setColor(l.getForeground()); + paintText(g, s, textX, textY); + } + + /** {inheritDoc} */ + protected void paintDisabledText(JLabel l, Graphics g, String s, int textX, + int textY) { + g.setColor(l.getBackground().darker()); + paintText(g, s, textX, textY); + } +} diff --git a/src/net/i2p/itoopie/gui/component/multilinelabel/ShadowLabelUI.java b/src/net/i2p/itoopie/gui/component/multilinelabel/ShadowLabelUI.java new file mode 100644 index 000000000..a2af8622e --- /dev/null +++ b/src/net/i2p/itoopie/gui/component/multilinelabel/ShadowLabelUI.java @@ -0,0 +1,71 @@ +package net.i2p.itoopie.gui.component.multilinelabel; + +/* + * The MIT License + * + * Copyright (c) 2009 Samuel Sjoberg + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import java.awt.Graphics; +import java.awt.Graphics2D; + +import javax.swing.JLabel; +import javax.swing.border.EmptyBorder; +import javax.swing.plaf.LabelUI; +import javax.swing.plaf.basic.BasicLabelUI; + +/** + * Label UI delegate painting the text with a subtle drop shadow. + * + * @author Samuel Sjoberg, http://samuelsjoberg.com + * @version 1.0.0 + */ +public class ShadowLabelUI extends BasicLabelUI { + + /** Static reference to the UI. */ + public static LabelUI labelUI = new ShadowLabelUI(); + + /** {@inheritDoc} */ + protected void installDefaults(JLabel c) { + super.installDefaults(c); + if (c.getBorder() == null) { + c.setBorder(new EmptyBorder(2, 2, 2, 2)); + } + } + + /** {@inheritDoc} */ + protected void paintDisabledText(JLabel l, Graphics g, String s, int textX, + int textY) { + g.setColor(l.getBackground().darker()); + g.translate(textX, textY); + Effects.paintTextShadow((Graphics2D) g, s); + g.translate(-textX, -textY); + } + + /** {@inheritDoc} */ + protected void paintEnabledText(JLabel l, Graphics g, String s, int textX, + int textY) { + g.setColor(l.getForeground()); + g.translate(textX, textY); + Effects.paintTextShadow((Graphics2D) g, s); + g.translate(-textX, -textY); + } +}