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

org.pushingpixels.radiance.component.internal.ui.common.BasicRichTooltipPanelUI Maven / Gradle / Ivy

/*
 * Copyright (c) 2005-2021 Radiance Kirill Grouchnikov. All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *  o Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 *  o Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 *  o Neither the name of the copyright holder nor the names of
 *    its contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package org.pushingpixels.radiance.component.internal.ui.common;

import org.pushingpixels.radiance.component.api.common.RichTooltip;
import org.pushingpixels.radiance.component.internal.utils.ComponentUtilities;
import org.jdesktop.swingx.icon.RadianceIcon;
import org.pushingpixels.radiance.theming.api.RadianceThemingCortex;
import org.pushingpixels.radiance.theming.internal.utils.border.RadianceBorder;

import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.plaf.UIResource;
import java.awt.*;
import java.awt.font.FontRenderContext;
import java.awt.font.LineBreakMeasurer;
import java.awt.font.TextAttribute;
import java.awt.font.TextLayout;
import java.awt.geom.AffineTransform;
import java.text.AttributedString;
import java.util.ArrayList;
import java.util.logging.Logger;

/**
 * Basic UI for rich tooltip panel {@link JRichTooltipPanel}.
 *
 * @author Kirill Grouchnikov
 */
public abstract class BasicRichTooltipPanelUI extends RichTooltipPanelUI {
	
	private static final Logger LOG = Logger.getLogger(BasicRichTooltipPanelUI.class.getName());

    /**
     * The associated tooltip panel.
     */
    protected JRichTooltipPanel richTooltipPanel;

    private java.util.List titleLabels;

    private java.util.List descriptionLabels;

    private JLabel mainImageLabel;

    private JSeparator footerSeparator;

    private JLabel footerImageLabel;

    private java.util.List footerLabels;

    public BasicRichTooltipPanelUI() {
        this.titleLabels = new ArrayList<>();
        this.descriptionLabels = new ArrayList<>();
        this.footerLabels = new ArrayList<>();
    }

    @Override
    public void installUI(JComponent c) {
        this.richTooltipPanel = (JRichTooltipPanel) c;
        super.installUI(this.richTooltipPanel);
        installDefaults();
        installComponents();
        installListeners();

        this.richTooltipPanel.setLayout(createLayoutManager());
    }

    @Override
    public void uninstallUI(JComponent c) {
        uninstallListeners();
        uninstallComponents();
        uninstallDefaults();
        super.uninstallUI(this.richTooltipPanel);
    }

    /**
     * Installs default settings for the associated rich tooltip panel.
     */
    protected void installDefaults() {
        Border b = this.richTooltipPanel.getBorder();
        if (b == null || b instanceof UIResource) {
            this.richTooltipPanel.setBorder(new RadianceBorder(new Insets(3, 5, 4, 5)));
        }
        LookAndFeel.installProperty(this.richTooltipPanel, "opaque", Boolean.TRUE);
        Font f = this.richTooltipPanel.getFont();
        if (f == null || f instanceof UIResource) {
            this.richTooltipPanel.setFont(
                    RadianceThemingCortex.GlobalScope.getFontPolicy().getFontSet().getControlFont());
        }
    }

    /**
     * Installs listeners on the associated rich tooltip panel.
     */
    protected void installListeners() {
    }

    /**
     * Installs components on the associated rich tooltip panel.
     */
    protected void installComponents() {
    }

    /**
     * Uninstalls default settings from the associated rich tooltip panel.
     */
    protected void uninstallDefaults() {
        LookAndFeel.uninstallBorder(this.richTooltipPanel);
    }

    /**
     * Uninstalls listeners from the associated rich tooltip panel.
     */
    protected void uninstallListeners() {
    }

    /**
     * Uninstalls subcomponents from the associated rich tooltip panel.
     */
    protected void uninstallComponents() {
        this.removeExistingComponents();
    }

    @Override
    public void update(Graphics g, JComponent c) {
        this.paintBackground(g);
        this.paint(g, c);
    }

    protected abstract void paintBackground(Graphics g);

    @Override
    public void paint(Graphics g, JComponent c) {
    }

    private LayoutManager createLayoutManager() {
        return new RichTooltipPanelLayout();
    }

    private class RichTooltipPanelLayout implements LayoutManager {
        @Override
        public void addLayoutComponent(String name, Component comp) {
        }

        @Override
        public void removeLayoutComponent(Component comp) {
        }

        @Override
        public Dimension minimumLayoutSize(Container parent) {
            return this.preferredLayoutSize(parent);
        }

        @Override
        public Dimension preferredLayoutSize(Container parent) {
            Insets ins = parent.getInsets();
            int gap = getLayoutGap();
            Font font = RadianceThemingCortex.GlobalScope.getFontPolicy().getFontSet().
                    getControlFont();
            Font titleFont = font.deriveFont(Font.BOLD);

            // The overall width is defined by the width of the text
            // and the presence of the main image.
            // The height is defined based on the width and the
            // text broken into multiline paragraphs

            int descTextWidth = getDescriptionTextWidth();
            int width = ins.left + 2 * gap + descTextWidth + ins.right;
            RichTooltip tooltipInfo = richTooltipPanel.getTooltipInfo();
            FontRenderContext frc = new FontRenderContext(new AffineTransform(), true, false);
            if(tooltipInfo==null) {
            	LOG.warning("!!!!!!!!!!!!!!! tooltipInfo==null");
            } else if (tooltipInfo.getMainIcon() != null) {
                width += tooltipInfo.getMainIcon().getIconWidth();
            }

            int fontHeight = parent.getFontMetrics(font).getHeight();

            int height = ins.top;

            // The title label
            int titleTextHeight = 0;
            AttributedString titleAttributedDescription = new AttributedString(
                    tooltipInfo.getTitle());
            titleAttributedDescription.addAttribute(TextAttribute.FONT,
                    titleFont);
            LineBreakMeasurer titleLineBreakMeasurer = new LineBreakMeasurer(
                    titleAttributedDescription.getIterator(), frc);
            int maxTitleLineWidth = 0;
            while (true) {
                TextLayout tl = titleLineBreakMeasurer
                        .nextLayout(descTextWidth);
                if (tl == null) {
                    break;
                }
                titleTextHeight += fontHeight;
                int lineWidth = (int) Math.ceil(tl.getBounds().getWidth());
                maxTitleLineWidth = Math.max(maxTitleLineWidth, lineWidth);
            }
            height += titleTextHeight;

            // The description text
            int descriptionTextHeight = 0;
            for (String descText : tooltipInfo.getDescriptionSections()) {
                AttributedString descAttributedDescription = new AttributedString(descText);
                descAttributedDescription.addAttribute(TextAttribute.FONT, font);
                LineBreakMeasurer descLineBreakMeasurer = new LineBreakMeasurer(
                        descAttributedDescription.getIterator(), frc);
                while (true) {
                    TextLayout tl = descLineBreakMeasurer
                            .nextLayout(descTextWidth);
                    if (tl == null) {
                        break;
                    }
                    descriptionTextHeight += fontHeight;
                }
                // add an empty line after the paragraph
                descriptionTextHeight += fontHeight;
            }

            if (!tooltipInfo.getDescriptionSections().isEmpty()) {
                // remove the empty line after the last paragraph
                descriptionTextHeight -= fontHeight;
                // add gap between the title and the description
                descriptionTextHeight += gap;
            }

            if (tooltipInfo.getMainIcon() != null) {
                height += Math.max(descriptionTextHeight,
                        tooltipInfo.getMainIcon().getIconHeight());
            } else {
                height += descriptionTextHeight;
            }

            if ((tooltipInfo.getFooterIcon() != null)
                    || (tooltipInfo.getFooterSections().size() > 0)) {
                height += gap;
                // The footer separator
                height += new JSeparator(JSeparator.HORIZONTAL).getPreferredSize().height;

                height += gap;

                int footerTextHeight = 0;
                int availableWidth = descTextWidth;
                if (tooltipInfo.getFooterIcon() != null) {
                    availableWidth -= 16;
                }
                if (tooltipInfo.getMainIcon() != null) {
                    availableWidth += tooltipInfo.getMainIcon().getIconWidth();
                }
                for (String footerText : tooltipInfo.getFooterSections()) {
                    AttributedString footerAttributedDescription = new AttributedString(footerText);
                    footerAttributedDescription.addAttribute(TextAttribute.FONT, font);
                    LineBreakMeasurer footerLineBreakMeasurer = new LineBreakMeasurer(
                            footerAttributedDescription.getIterator(), frc);
                    while (true) {
                        TextLayout tl = footerLineBreakMeasurer.nextLayout(availableWidth);
                        if (tl == null) {
                            break;
                        }
                        footerTextHeight += fontHeight;
                    }
                    // add an empty line after the paragraph
                    footerTextHeight += fontHeight;
                }
                // remove the empty line after the last paragraph
                footerTextHeight -= fontHeight;

                if (tooltipInfo.getFooterIcon() != null) {
                    height += Math.max(footerTextHeight, 16);
                } else {
                    height += footerTextHeight;
                }
            }

            height += ins.bottom;

            // special case for rich tooltips that only have titles
            if (tooltipInfo.getDescriptionSections().isEmpty()
                    && (tooltipInfo.getMainIcon() == null)
                    && tooltipInfo.getFooterSections().isEmpty()
                    && (tooltipInfo.getFooterIcon() == null)) {
                width = maxTitleLineWidth + 1 + ins.left + ins.right;
            }

            return new Dimension(width, height);
        }

        @Override
        public void layoutContainer(Container parent) {
            if(parent.getWidth()==1 && parent.getHeight()==1) { 
            	// see https://github.com/kirill-grouchnikov/radiance/issues/413
            	LOG.warning("parent Container is 1x1:"+parent);
                return;
            }
        	
            removeExistingComponents();

            Font font = RadianceThemingCortex.GlobalScope.getFontPolicy().getFontSet().
                    getControlFont();
            Insets ins = richTooltipPanel.getInsets();
            int y = ins.top;
            RichTooltip tooltipInfo = richTooltipPanel.getTooltipInfo();
            FontRenderContext frc = new FontRenderContext(new AffineTransform(), true, false);
            int gap = getLayoutGap();

            int fontHeight = parent.getFontMetrics(font).getHeight();
            Font titleFont = font.deriveFont(Font.BOLD);

            boolean ltr = richTooltipPanel.getComponentOrientation().isLeftToRight();

            // The title label
            int titleLabelWidth = parent.getWidth() - ins.left - ins.right;
            AttributedString titleAtributedDescription = new AttributedString(
                    tooltipInfo.getTitle());
            titleAtributedDescription.addAttribute(TextAttribute.FONT, titleFont);
            LineBreakMeasurer titleLineBreakMeasurer = new LineBreakMeasurer(
                    titleAtributedDescription.getIterator(), frc);
            int titleCurrOffset = 0;
            while (true) {
                TextLayout tl = titleLineBreakMeasurer.nextLayout(titleLabelWidth);
                if (tl == null) {
                    break;
                }
                int charCount = tl.getCharacterCount();
                String line = tooltipInfo.getTitle().substring(titleCurrOffset,
                        titleCurrOffset + charCount);

                JLabel titleLabel = new JLabel(line);
                titleLabel.setFont(titleFont);
                titleLabels.add(titleLabel);
                richTooltipPanel.add(titleLabel);
                int currLabelWidth = titleLabel.getPreferredSize().width;
                if (ltr) {
                    titleLabel.setBounds(ins.left, y, currLabelWidth,
                            fontHeight);
                } else {
                    titleLabel.setBounds(parent.getWidth() - ins.right
                            - currLabelWidth, y, currLabelWidth, fontHeight);
                }
                y += titleLabel.getHeight();

                titleCurrOffset += charCount;
            }
            y += gap;

            // The main image
            int x = ltr ? ins.left : parent.getWidth() - ins.right;
            RadianceIcon mainImage = tooltipInfo.getMainIcon();
            if (mainImage != null) {
                mainImageLabel = new JLabel(mainImage);
                richTooltipPanel.add(mainImageLabel);
                int mainImageWidth = mainImageLabel.getPreferredSize().width;
                if (ltr) {
                    mainImageLabel.setBounds(x, y, mainImageWidth,
                            mainImageLabel.getPreferredSize().height);
                    x += mainImageWidth;
                } else {
                    mainImageLabel.setBounds(x - mainImageWidth, y,
                            mainImageWidth,
                            mainImageLabel.getPreferredSize().height);
                    x -= mainImageWidth;
                }
            }
            if (ltr) {
                x += 2 * gap;
            } else {
                x -= 2 * gap;
            }

            // The description text
            int descLabelWidth = ltr ? parent.getWidth() - x - ins.right : x - ins.left;
            for (String descText : tooltipInfo.getDescriptionSections()) {
                AttributedString attributedDescription = new AttributedString(descText);
                attributedDescription.addAttribute(TextAttribute.FONT, font);
                LineBreakMeasurer lineBreakMeasurer = new LineBreakMeasurer(
                        attributedDescription.getIterator(), frc);
                int currOffset = 0;
                while (true) {
                    TextLayout tl = lineBreakMeasurer.nextLayout(descLabelWidth);
                    if (tl == null) {
                        break;
                    }
                    int charCount = tl.getCharacterCount();
                    String line = descText.substring(currOffset, currOffset + charCount);

                    JLabel descLabel = new JLabel(line);
                    descriptionLabels.add(descLabel);
                    richTooltipPanel.add(descLabel);
                    int currDescWidth = descLabel.getPreferredSize().width;
                    if (ltr) {
                        descLabel.setBounds(x, y, currDescWidth, fontHeight);
                    } else {
                        descLabel.setBounds(x - currDescWidth, y, currDescWidth, fontHeight);
                    }
                    y += descLabel.getHeight();

                    currOffset += charCount;
                }
                // add an empty line after the paragraph
                y += fontHeight;
            }
            // remove the empty line after the last paragraph
            y -= fontHeight;

            if (mainImageLabel != null) {
                y = Math.max(y, mainImageLabel.getY() + mainImageLabel.getHeight());
            }

            if ((tooltipInfo.getFooterIcon() != null)
                    || (tooltipInfo.getFooterSections().size() > 0)) {
                y += gap;
                // The footer separator
                footerSeparator = new JSeparator(JSeparator.HORIZONTAL);
                richTooltipPanel.add(footerSeparator);
                footerSeparator.setBounds(ins.left, y,
                        parent.getWidth() - ins.left - ins.right,
                        footerSeparator.getPreferredSize().height);

                y += footerSeparator.getHeight() + gap;

                // The footer image
                x = ltr ? ins.left : parent.getWidth() - ins.right;
                if (tooltipInfo.getFooterIcon() != null) {
                    tooltipInfo.getFooterIcon().setDimension(new Dimension(16, 16));
                    footerImageLabel = new JLabel(tooltipInfo.getFooterIcon());
                    richTooltipPanel.add(footerImageLabel);
                    int footerImageWidth = footerImageLabel.getPreferredSize().width;
                    if (ltr) {
                        footerImageLabel.setBounds(x, y, footerImageWidth,
                                footerImageLabel.getPreferredSize().height);
                        x += footerImageWidth + 2 * gap;
                    } else {
                        footerImageLabel.setBounds(x - footerImageWidth, y,
                                footerImageWidth, footerImageLabel.getPreferredSize().height);
                        x -= (footerImageWidth + 2 * gap);
                    }
                }

                // The footer text
                int footerLabelWidth = ltr ? parent.getWidth() - x - ins.right : x - ins.left;
                for (String footerText : tooltipInfo.getFooterSections()) {
                    AttributedString attributedDescription = new AttributedString(footerText);
                    attributedDescription.addAttribute(TextAttribute.FONT, font);
                    LineBreakMeasurer lineBreakMeasurer = new LineBreakMeasurer(
                            attributedDescription.getIterator(), frc);
                    int currOffset = 0;
                    while (true) {
                        TextLayout tl = lineBreakMeasurer.nextLayout(footerLabelWidth);
                        if (tl == null) {
                            break;
                        }
                        int charCount = tl.getCharacterCount();
                        String line = footerText.substring(currOffset, currOffset + charCount);

                        JLabel footerLabel = new JLabel(line);
                        footerLabels.add(footerLabel);
                        richTooltipPanel.add(footerLabel);
                        int currLabelWidth = footerLabel.getPreferredSize().width;
                        if (ltr) {
                            footerLabel.setBounds(x, y, currLabelWidth, fontHeight);
                        } else {
                            footerLabel.setBounds(x - currLabelWidth, y,
                                    currLabelWidth, fontHeight);
                        }
                        y += footerLabel.getHeight();

                        currOffset += charCount;
                    }
                    // add an empty line after the paragraph
                    y += fontHeight;
                }
                // remove the empty line after the last paragraph
                y -= fontHeight;
            }
        }
    }

    private int getDescriptionTextWidth() {
        return ComponentUtilities.getScaledSize(200,
                this.richTooltipPanel.getFont().getSize(), 40f, 4);
    }

    private int getLayoutGap() {
        return 4;
    }

    private void removeExistingComponents() {
        for (JLabel label : this.titleLabels)
            this.richTooltipPanel.remove(label);

        if (this.mainImageLabel != null) {
            this.richTooltipPanel.remove(this.mainImageLabel);
        }

        for (JLabel label : this.descriptionLabels)
            this.richTooltipPanel.remove(label);

        if (this.footerSeparator != null) {
            this.richTooltipPanel.remove(this.footerSeparator);
        }

        if (this.footerImageLabel != null) {
            this.richTooltipPanel.remove(this.footerImageLabel);
        }

        for (JLabel label : this.footerLabels)
            this.richTooltipPanel.remove(label);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy