001    // License: GPL. For details, see LICENSE file.
002    
003    package org.openstreetmap.josm.gui;
004    
005    import java.awt.Dimension;
006    import java.awt.Rectangle;
007    
008    import javax.swing.JLabel;
009    import javax.swing.plaf.basic.BasicHTML;
010    import javax.swing.text.View;
011    
012    /**
013     * Creates a normal label that will wrap its contents if there less width than
014     * required to print it in one line. Additionally the maximum width of the text
015     * can be set using <code>setMaxWidth</code>.
016     *
017     * Note that this won't work if JMultilineLabel is put into a JScrollBox or
018     * similar as the bounds will never change. Instead scrollbars will be displayed.
019     */
020    public class JMultilineLabel extends JLabel {
021        private int maxWidth = Integer.MAX_VALUE;
022        private Dimension superPreferred = null;
023        private Rectangle oldbounds = null;
024        private Dimension oldPreferred = null;
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
033         */
034        public JMultilineLabel(String text)
035        {
036            super();
037            text = text.trim().replaceAll("\n", "<br>");
038            if(!text.startsWith("<html>")) {
039                text = "<html>" + text + "</html>";
040            }
041            super.setText(text);
042        }
043    
044        /**
045         * Set the maximum width. Use this method instead of setMaximumSize because
046         * this saves a little bit of overhead and is actually taken into account.
047         *
048         * @param width
049         */
050        public void setMaxWidth(int width) {
051            this.maxWidth = width;
052        }
053    
054        /**
055         * Tries to determine a suitable height for the given contents and return
056         * that dimension.
057         */
058        @Override
059        public Dimension getPreferredSize()
060        {
061            // Without this check it will result in an infinite loop calling
062            // getPreferredSize. Remember the old bounds and only recalculate if
063            // the size actually changed.
064            if(this.getBounds().equals(oldbounds) && oldPreferred != null)
065                return oldPreferred;
066            oldbounds = this.getBounds();
067    
068            this.superPreferred = super.getPreferredSize();
069            // Make it not larger than required
070            int width = Math.min(superPreferred.width, maxWidth);
071    
072            // Calculate suitable width and height
073            final View v = (View) super.getClientProperty(BasicHTML.propertyKey);
074    
075            if(v == null)
076                return superPreferred;
077    
078            v.setSize(width, 0);
079            int w = (int) Math.ceil(v.getPreferredSpan(View.X_AXIS));
080            int h = (int) Math.ceil(v.getPreferredSpan(View.Y_AXIS));
081    
082            oldPreferred = new Dimension(w, h);
083            return oldPreferred;
084        }
085    }