All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.alee.extended.label.StyledLabelUtils Maven / Gradle / Ivy

Go to download

WebLaf is a Java Swing Look and Feel and extended components library for cross-platform applications

There is a newer version: 2.2.1
Show newest version
/*
 * This file is part of WebLookAndFeel library.
 *
 * WebLookAndFeel library 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 3 of the License, or
 * (at your option) any later version.
 *
 * WebLookAndFeel library 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 WebLookAndFeel library.  If not, see .
 */

package com.alee.extended.label;

import com.alee.utils.TextUtils;
import com.alee.utils.xml.ColorConverter;

import javax.swing.*;
import javax.swing.text.View;
import java.awt.*;
import java.util.*;
import java.util.List;

/**
 * This class provides a set of utilities for WebStyledLabel component.
 *
 * @author Mikle Garin
 */

public final class StyledLabelUtils implements SwingConstants
{
    /**
     * Custom StyleRange comparator.
     * Compares style ranges by their positions.
     */
    private static final Comparator styleRangeComparator = new StyleRangeComparator ();

    /**
     * Parses label style ranges into text ranges.
     * All parsed text ranges are stored within provided list.
     *
     * @param label      label to parse text ranges for
     * @param textRanges list to store text ranges into
     */
    public static void buildTextRanges ( final WebStyledLabel label, final List textRanges )
    {
        textRanges.clear ();

        // Sorting style ranges by their positions
        final List styleRanges = label.getStyleRanges ();
        Collections.sort ( styleRanges, styleRangeComparator );

        // Checking whether text is empty or not
        final String s = label.getText ();
        if ( s != null && s.length () > 0 )
        {
            int index = 0;
            for ( final StyleRange styleRange : styleRanges )
            {
                if ( index >= s.length () )
                {
                    break;
                }

                // Add text range for the gap between current and previous style
                if ( styleRange.getStartIndex () > index )
                {
                    final String text = s.substring ( index, Math.min ( styleRange.getStartIndex (), s.length () ) );
                    final StyleRange newRange = new StyleRange ( index, styleRange.getStartIndex () - index );
                    addStyledTexts ( text, newRange, textRanges );
                    index = styleRange.getStartIndex ();
                }

                // Add text range for customized style
                if ( styleRange.getStartIndex () == index )
                {
                    // Either till the end or not
                    if ( styleRange.getLength () == -1 )
                    {
                        final String text = s.substring ( index );
                        addStyledTexts ( text, styleRange, textRanges );
                        index = s.length ();
                    }
                    else
                    {
                        final String text = s.substring ( index, Math.min ( index + styleRange.getLength (), s.length () ) );
                        addStyledTexts ( text, styleRange, textRanges );
                        index += styleRange.getLength ();
                    }
                }
            }

            // Add enclosing text range
            if ( index < s.length () )
            {
                final String text = s.substring ( index, s.length () );
                final StyleRange range = new StyleRange ( index, s.length () - index );
                addStyledTexts ( text, range, textRanges );
            }
        }
    }

    private static void addStyledTexts ( String text, StyleRange range, final List textRanges )
    {
        // Copying style range to avoid original parameters changes
        range = new StyleRange ( range );
        int index1 = text.indexOf ( '\r' );
        int index2 = text.indexOf ( '\n' );

        while ( index1 >= 0 || index2 >= 0 )
        {
            int index = index1 >= 0 ? index1 : -1;
            if ( index2 >= 0 && ( index2 < index1 || index < 0 ) )
            {
                index = index2;
            }

            final String subString = text.substring ( 0, index );
            StyleRange newRange = new StyleRange ( range );
            newRange.setStartIndex ( range.getStartIndex () );
            newRange.setLength ( index );
            textRanges.add ( new TextRange ( subString, newRange ) );

            int length = 1;
            if ( text.charAt ( index ) == '\r' && index + 1 < text.length () && text.charAt ( index + 1 ) == '\n' )
            {
                length++;
            }

            newRange = new StyleRange ( range );
            newRange.setStartIndex ( range.getStartIndex () + index );
            newRange.setLength ( length );
            textRanges.add ( new TextRange ( text.substring ( index, index + length ), newRange ) );

            text = text.substring ( index + length );
            range.setStartIndex ( range.getStartIndex () + index + length );
            range.setLength ( range.getLength () - index - length );

            index1 = text.indexOf ( '\r' );
            index2 = text.indexOf ( '\n' );
        }
        if ( text.length () > 0 )
        {
            textRanges.add ( new TextRange ( text, range ) );
        }
    }

    /**
     * Returns label custom font if exists or default label font otherwise.
     *
     * @param label label to retrieve font for
     * @return label custom font if exists or default label font otherwise
     */
    public static Font getFont ( final WebStyledLabel label )
    {
        Font font = label.getFont ();
        if ( font == null )
        {
            font = UIManager.getFont ( "Label.font" );
        }
        return font;
    }

    public static int findNextWordStartIndex ( final String string, final int firstRowEndIndex )
    {
        boolean skipFirstWord = firstRowEndIndex < 0;
        for ( int i = firstRowEndIndex + 1; i < string.length (); i++ )
        {
            final char c = string.charAt ( i );
            if ( c != ' ' && c != '\t' && c != '\r' && c != '\n' )
            {
                if ( !skipFirstWord )
                {
                    return i;
                }
            }
            else
            {
                skipFirstWord = false;
            }
        }
        return string.length ();
    }

    public static int findFirstRowWordEndIndex ( final String string )
    {
        boolean spaceFound = false;
        for ( int i = string.length () - 1; i >= 0; i-- )
        {
            final char c = string.charAt ( i );
            if ( !spaceFound )
            {
                if ( c == ' ' || c == '\t' || c == '\r' || c == '\n' )
                {
                    spaceFound = true;
                }
            }
            else
            {
                if ( c != ' ' && c != '\t' && c != '\r' && c != '\n' )
                {
                    return i;
                }
            }
        }
        return -1;
    }

    /**
     * Computes compound label icon and text positions relative to viewR rectangle.
     * Component orientation is also taken into account and properly applied.
     *
     * @param label                  painted label
     * @param text                   painted text
     * @param icon                   painted icon
     * @param verticalAlignment      vertical alignment
     * @param horizontalAlignment    horizontal alignment
     * @param verticalTextPosition   vertical text position
     * @param horizontalTextPosition horizontal text position
     * @param viewR                  view rectangle
     * @param iconR                  icon rectangle
     * @param textR                  text rectangle
     * @param textIconGap            gap between text and icon
     * @return layout string
     */
    public static String layoutCompoundLabel ( final WebStyledLabel label, final String text, final Icon icon, final int verticalAlignment,
                                               final int horizontalAlignment, final int verticalTextPosition,
                                               final int horizontalTextPosition, final Rectangle viewR, final Rectangle iconR,
                                               final Rectangle textR, final int textIconGap )
    {
        boolean orientationIsLeftToRight = true;
        int hAlign = horizontalAlignment;
        int hTextPos = horizontalTextPosition;

        if ( label != null )
        {
            if ( !label.getComponentOrientation ().isLeftToRight () )
            {
                orientationIsLeftToRight = false;
            }
        }

        // Translate LEADING/TRAILING values in horizontalAlignment to LEFT/RIGHT values depending on the components orientation
        switch ( horizontalAlignment )
        {
            case LEADING:
                hAlign = ( orientationIsLeftToRight ) ? LEFT : RIGHT;
                break;
            case TRAILING:
                hAlign = ( orientationIsLeftToRight ) ? RIGHT : LEFT;
                break;
        }

        // Translate LEADING/TRAILING values in horizontalTextPosition to LEFT/RIGHT values depending on the components orientation
        switch ( horizontalTextPosition )
        {
            case LEADING:
                hTextPos = ( orientationIsLeftToRight ) ? LEFT : RIGHT;
                break;
            case TRAILING:
                hTextPos = ( orientationIsLeftToRight ) ? RIGHT : LEFT;
                break;
        }

        return layoutCompoundLabelImpl ( label, text, icon, verticalAlignment, hAlign, verticalTextPosition, hTextPos, viewR, iconR, textR,
                textIconGap );
    }

    /**
     * Computes compound label icon and text positions relative to viewR rectangle.
     *
     * @param text                   painted text
     * @param icon                   painted icon
     * @param verticalAlignment      vertical alignment
     * @param horizontalAlignment    horizontal alignment
     * @param verticalTextPosition   vertical text position
     * @param horizontalTextPosition horizontal text position
     * @param viewR                  view rectangle
     * @param iconR                  icon rectangle
     * @param textR                  text rectangle
     * @param textIconGap            gap between text and icon
     * @return layout string
     */
    public static String layoutCompoundLabel ( final String text, final Icon icon, final int verticalAlignment,
                                               final int horizontalAlignment, final int verticalTextPosition,
                                               final int horizontalTextPosition, final Rectangle viewR, final Rectangle iconR,
                                               final Rectangle textR, final int textIconGap )
    {
        return layoutCompoundLabelImpl ( null, text, icon, verticalAlignment, horizontalAlignment, verticalTextPosition,
                horizontalTextPosition, viewR, iconR, textR, textIconGap );
    }

    /**
     * Compute and return the location of the icons origin, the location of origin of the text baseline, and a possibly
     * clipped version of the compound labels string.  Locations are computed relative to the viewR rectangle. This
     * layoutCompoundLabel() does not know how to handle LEADING/TRAILING values in horizontalTextPosition (they will
     * default to RIGHT) and in horizontalAlignment (they will default to CENTER). Use the other version of
     * layoutCompoundLabel() instead.
     *
     * @param label                  painted label
     * @param text                   painted text
     * @param icon                   painted icon
     * @param verticalAlignment      vertical alignment
     * @param horizontalAlignment    horizontal alignment
     * @param verticalTextPosition   vertical text position
     * @param horizontalTextPosition horizontal text position
     * @param viewR                  view rectangle
     * @param iconR                  icon rectangle
     * @param textR                  text rectangle
     * @param textIconGap            gap between text and icon
     * @return the layout string
     */
    private static String layoutCompoundLabelImpl ( final WebStyledLabel label, String text, final Icon icon, final int verticalAlignment,
                                                    final int horizontalAlignment, final int verticalTextPosition,
                                                    final int horizontalTextPosition, final Rectangle viewR, final Rectangle iconR,
                                                    final Rectangle textR, final int textIconGap )
    {
        // Initialize the icon bounds rectangle iconR.
        if ( icon != null )
        {
            iconR.width = icon.getIconWidth ();
            iconR.height = icon.getIconHeight ();
        }
        else
        {
            iconR.width = iconR.height = 0;
        }

        // Initialize the text bounds rectangle textR
        // If a null or and empty String was specified we substitute "" here and use 0,0,0,0 for textR
        final boolean textIsEmpty = ( text == null ) || text.equals ( "" );
        final int lsb = 0;

        // Unless both text and icon are non-null, the value of textIconGap is ignored
        final int gap;

        final View v;
        if ( textIsEmpty )
        {
            textR.width = textR.height = 0;
            text = "";
            gap = 0;
        }
        else
        {
            final int availTextWidth;
            gap = ( icon == null ) ? 0 : textIconGap;

            if ( horizontalTextPosition == CENTER )
            {
                availTextWidth = viewR.width;
            }
            else
            {
                availTextWidth = viewR.width - ( iconR.width + gap );
            }
            v = ( label != null ) ? ( View ) label.getClientProperty ( "html" ) : null;
            if ( v != null )
            {
                textR.width = Math.min ( availTextWidth, ( int ) v.getPreferredSpan ( View.X_AXIS ) );
                textR.height = ( int ) v.getPreferredSpan ( View.Y_AXIS );
            }
        }

        // Compute textR.x,y given the verticalTextPosition and horizontalTextPosition properties
        if ( verticalTextPosition == TOP )
        {
            if ( horizontalTextPosition != CENTER )
            {
                textR.y = 0;
            }
            else
            {
                textR.y = -( textR.height + gap );
            }
        }
        else if ( verticalTextPosition == CENTER )
        {
            textR.y = ( iconR.height / 2 ) - ( textR.height / 2 );
        }
        else
        {
            if ( horizontalTextPosition != CENTER )
            {
                textR.y = iconR.height - textR.height;
            }
            else
            {
                textR.y = iconR.height + gap;
            }
        }

        if ( horizontalTextPosition == LEFT )
        {
            textR.x = -( textR.width + gap );
        }
        else if ( horizontalTextPosition == CENTER )
        {
            textR.x = ( iconR.width / 2 ) - ( textR.width / 2 );
        }
        else
        {
            textR.x = iconR.width + gap;
        }

        // labelR is the rectangle that contains iconR and textR
        // Move it to its proper position given the labelAlignment properties
        // To avoid actually allocating a Rectangle, Rectangle.union has been inlined below
        final int labelR_x = Math.min ( iconR.x, textR.x );
        final int labelR_width = Math.max ( iconR.x + iconR.width, textR.x + textR.width ) - labelR_x;
        final int labelR_y = Math.min ( iconR.y, textR.y );
        final int labelR_height = Math.max ( iconR.y + iconR.height, textR.y + textR.height ) - labelR_y;

        final int dx;
        final int dy;

        if ( verticalAlignment == TOP )
        {
            dy = viewR.y - labelR_y;
        }
        else if ( verticalAlignment == CENTER )
        {
            dy = ( viewR.y + ( viewR.height / 2 ) ) - ( labelR_y + ( labelR_height / 2 ) );
        }
        else
        {
            dy = ( viewR.y + viewR.height ) - ( labelR_y + labelR_height );
        }

        if ( horizontalAlignment == LEFT )
        {
            dx = viewR.x - labelR_x;
        }
        else if ( horizontalAlignment == RIGHT )
        {
            dx = ( viewR.x + viewR.width ) - ( labelR_x + labelR_width );
        }
        else
        {
            dx = ( viewR.x + ( viewR.width / 2 ) ) - ( labelR_x + ( labelR_width / 2 ) );
        }

        // Translate textR and iconR by dx,dy.
        textR.x += dx;
        textR.y += dy;
        iconR.x += dx;
        iconR.y += dy;

        if ( lsb < 0 )
        {
            // lsb is negative. Shift the x location so that the text is
            // visually drawn at the right location.
            textR.x -= lsb;
        }

        int maxIconY = viewR.height / 2;
        final Insets insets = label.getInsets ();
        final int leftMostX = viewR.x;
        int rightMostX = viewR.x + viewR.width;
        rightMostX -= iconR.width;
        if ( horizontalTextPosition == SwingConstants.CENTER )
        {
            if ( viewR.width < textR.width )
            {
                iconR.x = ( leftMostX + rightMostX ) / 2;
            }
            else
            {
                iconR.x = textR.x + ( textR.width - iconR.width ) / 2;
            }
        }
        else if ( iconR.x < leftMostX )
        {
            textR.x += leftMostX - iconR.x;
            iconR.x = leftMostX;
        }
        else if ( iconR.x > rightMostX && horizontalAlignment != LEFT )
        {
            textR.x -= iconR.x - rightMostX;
            iconR.x = rightMostX;
        }
        if ( insets != null )
        {
            maxIconY -= ( insets.bottom + insets.top ) / 2;
        }
        if ( icon != null )
        {
            maxIconY -= icon.getIconHeight () / 2;
        }
        if ( verticalAlignment == TOP )
        {
            iconR.y = Math.min ( maxIconY, iconR.y );
        }
        else if ( verticalAlignment == BOTTOM )
        {
            iconR.y = Math.max ( maxIconY, iconR.y );
        }

        return text;
    }

    public static String getPlainText ( final String text, final List styles )
    {
        if ( TextUtils.isEmpty ( text ) )
        {
            return text;
        }

        String plainText = "";
        String trimmedText = text;
        int begin = nextUnescaped ( trimmedText, "{", 0 );
        if ( begin != -1 )
        {
            while ( begin != -1 )
            {
                final int end = nextUnescaped ( trimmedText, "}", begin + 1 );
                if ( end != -1 )
                {
                    // Parsing statement
                    final String statement = trimmedText.substring ( begin + 1, end );
                    final TextRange range = parseStatement ( statement );

                    // Including parsed statement or simple text
                    if ( range != null )
                    {
                        // We found some styles in the statement
                        // Adding text and style range and proceeding
                        plainText += trimmedText.substring ( 0, begin );
                        range.getStyleRange ().setStartIndex ( plainText.length () );
                        styles.add ( range.getStyleRange () );
                        plainText += range.getText ();
                    }
                    else
                    {
                        // We didn't find any style statements
                        if ( statement.equals ( "br" ) )
                        {
                            // Adding linebreak and proceeding
                            plainText += trimmedText.substring ( 0, begin ) + "\n";
                        }
                        else
                        {
                            // Adding plain text and proceeding
                            plainText += trimmedText.substring ( 0, begin ) + statement;
                        }
                    }

                    // Continue to next
                    trimmedText = trimmedText.substring ( end + 1 );
                    begin = nextUnescaped ( trimmedText, "{", 0 );
                }
                else
                {
                    break;
                }
            }
            plainText += trimmedText;
        }
        else
        {
            plainText = text;
        }
        return plainText;
    }

    private static int nextUnescaped ( final String trimmedText, final String pattern, final int from )
    {
        // todo
        return trimmedText.indexOf ( pattern, from );
    }

    private static TextRange parseStatement ( final String statement )
    {
        final int sep = statement.indexOf ( ":" );
        if ( sep != -1 )
        {
            try
            {
                final String text = statement.substring ( 0, sep );
                boolean p = false;
                boolean b = false;
                boolean i = false;
                Color fg = null;
                Color bg = null;
                final List customStyles = new ArrayList ();

                final String vars = statement.substring ( sep + 1 );
                final StringTokenizer st = new StringTokenizer ( vars, ";", false );
                int styles = 0;
                while ( st.hasMoreTokens () )
                {
                    final String token = st.nextToken ();
                    if ( token.equals ( "p" ) || token.equals ( "plain" ) )
                    {
                        p = true;
                        styles++;
                    }
                    else if ( token.equals ( "b" ) || token.equals ( "bold" ) )
                    {
                        b = true;
                        styles++;
                    }
                    else if ( token.equals ( "i" ) || token.equals ( "italic" ) )
                    {
                        i = true;
                        styles++;
                    }
                    else if ( token.equals ( "u" ) || token.equals ( "underlined" ) )
                    {
                        customStyles.add ( CustomStyle.underlined );
                        styles++;
                    }
                    else if ( token.equals ( "sp" ) || token.equals ( "sup" ) || token.equals ( "superscript" ) )
                    {
                        customStyles.add ( CustomStyle.superscript );
                        styles++;
                    }
                    else if ( token.equals ( "sb" ) || token.equals ( "sub" ) || token.equals ( "subscript" ) )
                    {
                        customStyles.add ( CustomStyle.subscript );
                        styles++;
                    }
                    else if ( token.equals ( "s" ) || token.equals ( "strike" ) )
                    {
                        customStyles.add ( CustomStyle.strikeThrough );
                        styles++;
                    }
                    else if ( token.equals ( "ds" ) || token.equals ( "doublestrike" ) )
                    {
                        customStyles.add ( CustomStyle.doubleStrikeThrough );
                        styles++;
                    }
                    else if ( token.startsWith ( "c" ) || token.startsWith ( "color" ) ||
                            token.startsWith ( "f" ) || token.startsWith ( "foreground" ) )
                    {
                        final Color color = parseColor ( token );
                        if ( color != null )
                        {
                            fg = color;
                            styles++;
                        }
                    }
                    else if ( token.startsWith ( "b" ) || token.startsWith ( "background" ) )
                    {
                        final Color color = parseColor ( token );
                        if ( color != null )
                        {
                            bg = color;
                            styles++;
                        }
                    }
                    // Other variables are simply ignored so far
                    // New possible variables might be added in future
                }

                // Create style range only if some style was actually found
                if ( styles > 0 )
                {
                    // Combining TextRange and StyleRange from retrieved data
                    final int style = b && i ? Font.BOLD | Font.ITALIC : b ? Font.BOLD : i ? Font.ITALIC : p ? Font.PLAIN : -1;
                    final CustomStyle[] cs = customStyles.toArray ( new CustomStyle[ customStyles.size () ] );
                    return new TextRange ( text, new StyleRange ( 0, text.length (), style, fg, bg, cs ) );
                }
                else
                {
                    return null;
                }
            }
            catch ( final Throwable e )
            {
                return null;
            }
        }
        else
        {
            return null;
        }
    }

    private static Color parseColor ( final String statement )
    {
        final int i1 = statement.indexOf ( "(" );
        final int i2 = statement.lastIndexOf ( ")" );
        if ( i1 != -1 && i2 != -1 )
        {
            try
            {
                final String colorString = statement.substring ( i1 + 1, i2 );
                return ColorConverter.colorFromString ( colorString );
            }
            catch ( final Throwable e )
            {
                return null;
            }
        }
        else
        {
            return null;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy