001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.widgets;
003
004import java.awt.Dimension;
005import java.awt.Rectangle;
006
007import javax.swing.JEditorPane;
008import javax.swing.plaf.basic.BasicHTML;
009import javax.swing.text.View;
010
011/**
012 * Creates a normal label that will wrap its contents if there less width than
013 * required to print it in one line. Additionally the maximum width of the text
014 * can be set using <code>setMaxWidth</code>.
015 *
016 * Note that this won't work if JMultilineLabel is put into a JScrollBox or
017 * similar as the bounds will never change. Instead scrollbars will be displayed.
018 *
019 * @since 6340
020 */
021public class JMultilineLabel extends JEditorPane {
022    private int maxWidth = Integer.MAX_VALUE;
023    private Rectangle oldbounds;
024    private Dimension oldPreferred;
025
026    /**
027     * Constructs a normal label but adds HTML tags if not already done so.
028     * Supports both newline characters (<code>\n</code>) as well as the HTML
029     * <code>&lt;br&gt;</code> to insert new lines.
030     *
031     * Use setMaxWidth to limit the width of the label.
032     * @param text The text to display
033     */
034    public JMultilineLabel(String text) {
035        this(text, false);
036    }
037
038    /**
039     * Constructs a normal label but adds HTML tags if not already done so.
040     * Supports both newline characters (<code>\n</code>) as well as the HTML
041     * <code>&lt;br&gt;</code> to insert new lines.
042     *
043     * Use setMaxWidth to limit the width of the label.
044     * @param text The text to display
045     * @param allBold If {@code true}, makes all text to be displayed in bold
046     */
047    public JMultilineLabel(String text, boolean allBold) {
048        this(text, allBold, false);
049    }
050
051    /**
052     * Constructs a normal label but adds HTML tags if not already done so.
053     * Supports both newline characters (<code>\n</code>) as well as the HTML
054     * <code>&lt;br&gt;</code> to insert new lines.
055     *
056     * Use setMaxWidth to limit the width of the label.
057     * @param text The text to display
058     * @param allBold If {@code true}, makes all text to be displayed in bold
059     * @param focusable indicates whether this label is focusable
060     * @since 13157
061     */
062    public JMultilineLabel(String text, boolean allBold, boolean focusable) {
063        JosmEditorPane.makeJLabelLike(this, allBold);
064        String html = text.trim().replaceAll("\n", "<br>");
065        if (!html.startsWith("<html>")) {
066            html = "<html>" + html + "</html>";
067        }
068        setFocusable(focusable);
069        super.setText(html);
070    }
071
072    /**
073     * Set the maximum width. Use this method instead of setMaximumSize because
074     * this saves a little bit of overhead and is actually taken into account.
075     *
076     * @param width the maximum width
077     */
078    public void setMaxWidth(int width) {
079        this.maxWidth = width;
080    }
081
082    /**
083     * Tries to determine a suitable height for the given contents and return that dimension.
084     */
085    @Override
086    public Dimension getPreferredSize() {
087        // Without this check it will result in an infinite loop calling getPreferredSize.
088        // Remember the old bounds and only recalculate if the size actually changed.
089        if (oldPreferred != null && this.getBounds().equals(oldbounds)) {
090            return oldPreferred;
091        }
092        oldbounds = this.getBounds();
093
094        Dimension superPreferred = super.getPreferredSize();
095        // Make it not larger than required
096        int width = Math.min(superPreferred.width, maxWidth);
097
098        // Calculate suitable width and height
099        final View v = (View) super.getClientProperty(BasicHTML.propertyKey);
100
101        if (v == null) {
102            return superPreferred;
103        }
104
105        v.setSize(width, 0);
106        int w = (int) Math.ceil(v.getPreferredSpan(View.X_AXIS));
107        int h = (int) Math.ceil(v.getPreferredSpan(View.Y_AXIS));
108
109        oldPreferred = new Dimension(w, h);
110        return oldPreferred;
111    }
112}