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

com.alee.laf.menu.AbstractMenuItemLayout Maven / Gradle / Ivy

There is a newer version: 1.2.14
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.laf.menu;

import com.alee.api.annotations.NotNull;
import com.alee.api.annotations.Nullable;
import com.alee.painter.decoration.IDecoration;
import com.alee.painter.decoration.layout.AbstractContentLayout;
import com.alee.painter.decoration.layout.ContentLayoutData;
import com.thoughtworks.xstream.annotations.XStreamAsAttribute;

import javax.swing.*;
import java.awt.*;

/**
 * Abstract implementation of simple menu item layout.
 * It only paints contents placed under {@link #ICON}, {@link #TEXT}, {@link #ACCELERATOR} and {@link #ARROW} constraints.
 * It doesn't use {@link JMenuItem} as base type to allow other custom menu items to use this layout if necessary.
 *
 * @param  component type
 * @param  decoration type
 * @param  layout type
 * @author Mikle Garin
 */
public abstract class AbstractMenuItemLayout, I extends AbstractMenuItemLayout>
        extends AbstractContentLayout
{
    /**
     * Layout constraints.
     */
    protected static final String STATE_ICON = "state-icon";
    protected static final String ICON = "icon";
    protected static final String TEXT = "text";
    protected static final String ACCELERATOR = "accelerator";
    protected static final String ARROW = "arrow";

    /**
     * Gap between state icon and icon contents.
     */
    @Nullable
    @XStreamAsAttribute
    protected Integer stateIconGap;

    /**
     * Gap between icon and text contents.
     */
    @Nullable
    @XStreamAsAttribute
    protected Integer iconTextGap;

    /**
     * Gap between text and accelerator contents.
     */
    @Nullable
    @XStreamAsAttribute
    protected Integer textAcceleratorGap;

    /**
     * Gap between text and arrow contents.
     */
    @Nullable
    @XStreamAsAttribute
    protected Integer textArrowGap;

    /**
     * Returns whether or not menu items text should be aligned by maximum icon size.
     *
     * @param c {@link JComponent} that is being painted
     * @param d {@link IDecoration} state
     * @return true if menu items text should be aligned by maximum icon size, false otherwise
     */
    protected abstract boolean isAlignTextByIcons ( @NotNull C c, @NotNull D d );

    /**
     * Returns {@link PopupMenuIcons} information.
     *
     * @param c {@link JComponent} that is being painted
     * @param d {@link IDecoration} state
     * @return {@link PopupMenuIcons} information
     */
    @NotNull
    protected abstract PopupMenuIcons getPopupMenuIcons ( @NotNull C c, @NotNull D d );

    /**
     * Returns gap between state and icon contents.
     *
     * @param c {@link JComponent} that is being painted
     * @param d {@link IDecoration} state
     * @return gap between state and icon contents
     */
    protected int getStateIconGap ( @NotNull final C c, @NotNull final D d )
    {
        return stateIconGap != null ? stateIconGap : getIconTextGap ( c, d );
    }

    /**
     * Returns gap between icon and text contents.
     *
     * @param c {@link JComponent} that is being painted
     * @param d {@link IDecoration} state
     * @return gap between icon and text contents
     */
    protected int getIconTextGap ( @NotNull final C c, @NotNull final D d )
    {
        return iconTextGap != null ? iconTextGap : 0;
    }

    /**
     * Returns between text and accelerator contents.
     *
     * @param c {@link JComponent} that is being painted
     * @param d {@link IDecoration} state
     * @return between text and accelerator contents
     */
    protected int getTextAcceleratorGap ( @NotNull final C c, @NotNull final D d )
    {
        return textAcceleratorGap != null ? textAcceleratorGap : 0;
    }

    /**
     * Returns between text and arrow contents.
     *
     * @param c {@link JComponent} that is being painted
     * @param d {@link IDecoration} state
     * @return between text and arrow contents
     */
    protected int getTextArrowGap ( @NotNull final C c, @NotNull final D d )
    {
        return textArrowGap != null ? textArrowGap : 0;
    }

    @NotNull
    @Override
    public ContentLayoutData layoutContent ( @NotNull final C c, @NotNull final D d, @NotNull final Rectangle bounds )
    {
        final ContentLayoutData layoutData = new ContentLayoutData ( 4 );
        final boolean ltr = c.getComponentOrientation ().isLeftToRight ();
        final Dimension available = new Dimension ( bounds.width, bounds.height );
        int x = ltr ? bounds.x : bounds.x + bounds.width;
        final boolean alignTextByIcons = isAlignTextByIcons ( c, d );
        final boolean hasStateIcon = !isEmpty ( c, d, STATE_ICON );
        final boolean hasIcon = !isEmpty ( c, d, ICON );
        final PopupMenuIcons popupMenuIcons = getPopupMenuIcons ( c, d );
        if ( hasStateIcon || alignTextByIcons && popupMenuIcons.hasBothIcons )
        {
            final Dimension ips = getPreferredSize ( c, d, available, STATE_ICON );
            if ( alignTextByIcons )
            {
                ips.width = Math.max ( ips.width, popupMenuIcons.maxStateIconWidth );
                if ( !popupMenuIcons.hasBothIcons )
                {
                    ips.width = Math.max ( ips.width, popupMenuIcons.maxIconWidth );
                }
            }
            x += ltr ? 0 : -ips.width;
            if ( hasStateIcon )
            {
                layoutData.put ( STATE_ICON, new Rectangle ( x, bounds.y, ips.width, bounds.height ) );
            }
            final int stateIconGap = getStateIconGap ( c, d );
            x += ltr ? ips.width + stateIconGap : -stateIconGap;
            available.width -= ips.width + stateIconGap;
        }
        if ( hasIcon || alignTextByIcons && popupMenuIcons.hasBothIcons )
        {
            final Dimension ips = getPreferredSize ( c, d, available, ICON );
            if ( alignTextByIcons )
            {
                ips.width = Math.max ( ips.width, popupMenuIcons.maxIconWidth );
                if ( !popupMenuIcons.hasBothIcons )
                {
                    ips.width = Math.max ( ips.width, popupMenuIcons.maxStateIconWidth );
                }
            }
            x += ltr ? 0 : -ips.width;
            if ( hasIcon )
            {
                layoutData.put ( ICON, new Rectangle ( x, bounds.y, ips.width, bounds.height ) );
            }
            final int iconTextGap = getIconTextGap ( c, d );
            x += ltr ? ips.width + iconTextGap : -iconTextGap;
            available.width -= ips.width + iconTextGap;
        }
        if ( !hasStateIcon && !hasIcon && alignTextByIcons && popupMenuIcons.hasAnyIcons && !popupMenuIcons.hasBothIcons )
        {
            final int iconTextGap = getIconTextGap ( c, d );
            final int maxIcon = Math.max ( popupMenuIcons.maxStateIconWidth, popupMenuIcons.maxIconWidth );
            x += ltr ? maxIcon + iconTextGap : -iconTextGap;
            available.width -= maxIcon + iconTextGap;
        }
        if ( !isEmpty ( c, d, ARROW ) )
        {
            final Dimension aps = getPreferredSize ( c, d, available, ARROW );
            final int ax = ltr ? x + available.width - aps.width : x - available.width;
            layoutData.put ( ARROW, new Rectangle ( ax, bounds.y, aps.width, bounds.height ) );
            available.width -= aps.width + getTextArrowGap ( c, d );
        }
        if ( !isEmpty ( c, d, ACCELERATOR ) )
        {
            final Dimension aps = getPreferredSize ( c, d, available, ACCELERATOR );
            final int ax = ltr ? x + available.width - aps.width : x - available.width;
            layoutData.put ( ACCELERATOR, new Rectangle ( ax, bounds.y, aps.width, bounds.height ) );
            available.width -= aps.width + getTextAcceleratorGap ( c, d );
        }
        if ( !isEmpty ( c, d, TEXT ) )
        {
            x += ltr ? 0 : -available.width;
            layoutData.put ( TEXT, new Rectangle ( x, bounds.y, available.width, bounds.height ) );
        }
        return layoutData;
    }

    @NotNull
    @Override
    protected Dimension getContentPreferredSize ( @NotNull final C c, @NotNull final D d, @NotNull final Dimension available )
    {
        final Dimension ps = new Dimension ();
        final boolean alignTextByIcons = isAlignTextByIcons ( c, d );
        final boolean hasStateIcon = !isEmpty ( c, d, STATE_ICON );
        final boolean hasIcon = !isEmpty ( c, d, ICON );
        final PopupMenuIcons popupMenuIcons = getPopupMenuIcons ( c, d );
        if ( hasStateIcon || alignTextByIcons && popupMenuIcons.hasBothIcons )
        {
            final Dimension ips = getPreferredSize ( c, d, available, STATE_ICON );
            if ( alignTextByIcons )
            {
                ips.width = Math.max ( ips.width, popupMenuIcons.maxStateIconWidth );
                if ( !popupMenuIcons.hasBothIcons )
                {
                    ips.width = Math.max ( ips.width, popupMenuIcons.maxIconWidth );
                }
            }
            ps.width += ips.width + getStateIconGap ( c, d );
            ps.height = Math.max ( ps.height, ips.height );
        }
        if ( hasIcon || alignTextByIcons && popupMenuIcons.hasBothIcons )
        {
            final Dimension ips = getPreferredSize ( c, d, available, ICON );
            if ( alignTextByIcons )
            {
                ips.width = Math.max ( ips.width, popupMenuIcons.maxIconWidth );
                if ( !popupMenuIcons.hasBothIcons )
                {
                    ips.width = Math.max ( ips.width, popupMenuIcons.maxStateIconWidth );
                }
            }
            ps.width += ips.width + getIconTextGap ( c, d );
            ps.height = Math.max ( ps.height, ips.height );
        }
        if ( !hasStateIcon && !hasIcon && alignTextByIcons && popupMenuIcons.hasAnyIcons && !popupMenuIcons.hasBothIcons )
        {
            final int maxIcon = Math.max ( popupMenuIcons.maxStateIconWidth, popupMenuIcons.maxIconWidth );
            ps.width += maxIcon + getIconTextGap ( c, d );
        }
        if ( !isEmpty ( c, d, TEXT ) )
        {
            final Dimension tps = getPreferredSize ( c, d, available, TEXT );
            ps.width += tps.width;
            ps.height = Math.max ( ps.height, tps.height );
        }
        if ( !isEmpty ( c, d, ACCELERATOR ) )
        {
            final Dimension aps = getPreferredSize ( c, d, available, ACCELERATOR );
            ps.width += aps.width + getTextAcceleratorGap ( c, d );
            ps.height = Math.max ( ps.height, aps.height );
        }
        if ( !isEmpty ( c, d, ARROW ) )
        {
            final Dimension aps = getPreferredSize ( c, d, available, ARROW );
            ps.width += aps.width + getTextArrowGap ( c, d );
            ps.height = Math.max ( ps.height, aps.height );
        }
        return ps;
    }

    /**
     * Information about state {@link Icon} and custom {@link Icon} of all menu items in {@link JPopupMenu}.
     */
    protected static class PopupMenuIcons
    {
        /**
         * Whether or not at least one menu item has a non-{@code null} state {@link Icon} or custom {@link Icon}.
         */
        public boolean hasAnyIcons;

        /**
         * Whether or not at least one menu item has both state {@link Icon} and custom {@link Icon}.
         */
        public boolean hasBothIcons;

        /**
         * Maximum width of state {@link Icon}s of all menu items in {@link JPopupMenu}.
         * Will simply be {@code 0} if no items in menu have state {@link Icon}.
         */
        public int maxStateIconWidth;

        /**
         * Maximum width of custom {@link Icon}s of all menu items in {@link JPopupMenu}.
         * Will simply be {@code 0} if no items in menu have custom {@link Icon}.
         */
        public int maxIconWidth;

        /**
         * Constructs new {@link PopupMenuIcons}.
         *
         * @param hasAnyIcons       whether or not at least one menu item has a non-{@code null} state {@link Icon} or custom {@link Icon}
         * @param hasBothIcons      whether or not at least one menu item has both state {@link Icon} and custom {@link Icon}
         * @param maxStateIconWidth maximum width of state {@link Icon}s of all menu items in {@link JPopupMenu}
         * @param maxIconWidth      maximum width of custom {@link Icon}s of all menu items in {@link JPopupMenu}
         */
        public PopupMenuIcons ( final boolean hasAnyIcons, final boolean hasBothIcons, final int maxStateIconWidth, final int maxIconWidth )
        {
            this.hasAnyIcons = hasAnyIcons;
            this.hasBothIcons = hasBothIcons;
            this.maxStateIconWidth = maxStateIconWidth;
            this.maxIconWidth = maxIconWidth;
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy