/* Copyright 2008-2010 Gephi Authors : Mathieu Bastian <mathieu.bastian@gephi.org> Website : http://www.gephi.org This file is part of Gephi. DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. Copyright 2011 Gephi Consortium. All rights reserved. The contents of this file are subject to the terms of either the GNU General Public License Version 3 only ("GPL") or the Common Development and Distribution License("CDDL") (collectively, the "License"). You may not use this file except in compliance with the License. You can obtain a copy of the License at http://gephi.org/about/legal/license-notice/ or /cddl-1.0.txt and /gpl-3.0.txt. See the License for the specific language governing permissions and limitations under the License. When distributing the software, include this License Header Notice in each file and include the License files at /cddl-1.0.txt and /gpl-3.0.txt. If applicable, add the following below the License Header, with the fields enclosed by brackets [] replaced by your own identifying information: "Portions Copyrighted [year] [name of copyright owner]" If you wish your version of this file to be governed by only the CDDL or only the GPL Version 3, indicate your decision by adding "[Contributor] elects to include this software in this distribution under the [CDDL or GPL Version 3] license." If you do not indicate a single choice of license, a recipient has the option to distribute your version of this file under either the CDDL, the GPL Version 3 or to extend the choice of license to its licensees as provided above. However, if you add GPL Version 3 code and therefore, elected the GPL Version 3 license, then the option applies only if the new code is made subject to such option by the copyright holder. Contributor(s): Portions Copyrighted 2011 Gephi Consortium. */ package org.gephi.ui.components.richtooltip; import java.awt.Color; import java.awt.Component; import java.awt.Container; import java.awt.Dimension; import java.awt.Font; import java.awt.GradientPaint; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Insets; import java.awt.LayoutManager; 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 javax.swing.ImageIcon; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JSeparator; import javax.swing.LookAndFeel; import javax.swing.UIManager; import javax.swing.border.Border; import javax.swing.border.EmptyBorder; import javax.swing.border.LineBorder; import javax.swing.plaf.BorderUIResource; import javax.swing.plaf.ComponentUI; import javax.swing.plaf.UIResource; import org.pushingpixels.flamingo.internal.utils.FlamingoUtilities; /** * * @author Mathieu Bastian */ class BasicRichTooltipPanelUI extends RichTooltipPanelUI { /** * The associated tooltip panel. */ protected JRichTooltipPanel richTooltipPanel; protected JLabel titleLabel; protected java.util.List<JLabel> descriptionLabels; protected JLabel mainImageLabel; protected JSeparator footerSeparator; protected JLabel footerImageLabel; protected java.util.List<JLabel> footerLabels; /* * (non-Javadoc) * * @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent) */ public static ComponentUI createUI(JComponent c) { return new BasicRichTooltipPanelUI(); } public BasicRichTooltipPanelUI() { this.descriptionLabels = new ArrayList<JLabel>(); this.footerLabels = new ArrayList<JLabel>(); } /* * (non-Javadoc) * * @see javax.swing.plaf.ComponentUI#installUI(javax.swing.JComponent) */ @Override public void installUI(JComponent c) { this.richTooltipPanel = (JRichTooltipPanel) c; super.installUI(this.richTooltipPanel); installDefaults(); installComponents(); installListeners(); this.richTooltipPanel.setLayout(createLayoutManager()); } /* * (non-Javadoc) * * @see javax.swing.plaf.ComponentUI#uninstallUI(javax.swing.JComponent) */ @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) { Border toSet = UIManager.getBorder("RichTooltipPanel.border"); if (toSet == null) toSet = new BorderUIResource.CompoundBorderUIResource( new LineBorder(FlamingoUtilities.getBorderColor()), new EmptyBorder(2, 4, 3, 4)); this.richTooltipPanel.setBorder(toSet); } LookAndFeel.installProperty(this.richTooltipPanel, "opaque", Boolean.TRUE); } /** * 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 void paintBackground(Graphics g) { Color main = FlamingoUtilities.getColor(Color.gray, "Label.disabledForeground").brighter(); Graphics2D g2d = (Graphics2D) g.create(); g2d.setPaint(new GradientPaint(0, 0, FlamingoUtilities.getLighterColor( main, 0.9), 0, this.richTooltipPanel.getHeight(), FlamingoUtilities.getLighterColor(main, 0.4))); g2d.fillRect(0, 0, this.richTooltipPanel.getWidth(), this.richTooltipPanel.getHeight()); g2d.setFont(FlamingoUtilities.getFont(this.richTooltipPanel, "Ribbon.font", "Button.font", "Panel.font")); g2d.dispose(); } @Override public void paint(Graphics g, JComponent c) { } protected LayoutManager createLayoutManager() { return new RichTooltipPanelLayout(); } protected 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 = FlamingoUtilities.getFont(parent, "Ribbon.font", "Button.font", "Panel.font"); // the main text gets 200 pixels. The width is defined // by this and the presence of the main text. // 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.getMainImage() != null) { width += tooltipInfo.getMainImage().getWidth(null); } int fontHeight = parent.getFontMetrics(font).getHeight(); int height = ins.top; // The title label height += fontHeight + gap; // The description text int descriptionTextHeight = 0; for (String descText : tooltipInfo.getDescriptionSections()) { AttributedString attributedDescription = new AttributedString( descText); attributedDescription.addAttribute(TextAttribute.FONT, font); LineBreakMeasurer lineBreakMeasurer = new LineBreakMeasurer( attributedDescription.getIterator(), frc); while (true) { TextLayout tl = lineBreakMeasurer.nextLayout(descTextWidth); if (tl == null) break; descriptionTextHeight += fontHeight; } // add an empty line after the paragraph descriptionTextHeight += fontHeight; } // remove the empty line after the last paragraph descriptionTextHeight -= fontHeight; if (tooltipInfo.getMainImage() != null) { height += Math.max(descriptionTextHeight, new JLabel( new ImageIcon(tooltipInfo.getMainImage())) .getPreferredSize().height); } else { height += descriptionTextHeight; } if ((tooltipInfo.getFooterImage() != 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.getFooterImage() != null) { availableWidth -= tooltipInfo.getFooterImage().getWidth( null); } if (tooltipInfo.getMainImage() != null) { availableWidth += tooltipInfo.getMainImage().getWidth(null); } for (String footerText : tooltipInfo.getFooterSections()) { AttributedString attributedDescription = new AttributedString( footerText); attributedDescription .addAttribute(TextAttribute.FONT, font); LineBreakMeasurer lineBreakMeasurer = new LineBreakMeasurer( attributedDescription.getIterator(), frc); while (true) { TextLayout tl = lineBreakMeasurer .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.getFooterImage() != null) { height += Math.max(footerTextHeight, new JLabel( new ImageIcon(tooltipInfo.getFooterImage())) .getPreferredSize().height); } else { height += footerTextHeight; } } height += ins.bottom; return new Dimension(width, height); } @Override public void layoutContainer(Container parent) { removeExistingComponents(); Font font = FlamingoUtilities.getFont(parent, "Ribbon.font", "Button.font", "Panel.font"); 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(); // The title label titleLabel = new JLabel(tooltipInfo.getTitle()); titleLabel.setFont(titleLabel.getFont().deriveFont(Font.BOLD)); richTooltipPanel.add(titleLabel); titleLabel.setBounds(ins.left, y, titleLabel.getPreferredSize().width, fontHeight); y += titleLabel.getHeight() + gap; // The main image int x = ins.left; if (tooltipInfo.getMainImage() != null) { mainImageLabel = new JLabel(new ImageIcon(tooltipInfo .getMainImage())); richTooltipPanel.add(mainImageLabel); mainImageLabel.setBounds(x, y, mainImageLabel .getPreferredSize().width, mainImageLabel .getPreferredSize().height); x += mainImageLabel.getWidth(); } x += 2 * gap; // The description text int descLabelWidth = parent.getWidth() - x - ins.right; 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); descLabel.setBounds(x, y, descLabel.getPreferredSize().width, fontHeight); y += descLabel.getHeight(); currOffset += charCount; } // add an empty line after the paragraph y += titleLabel.getHeight(); } // remove the empty line after the last paragraph y -= titleLabel.getHeight(); if (mainImageLabel != null) { y = Math.max(y, mainImageLabel.getY() + mainImageLabel.getHeight()); } if ((tooltipInfo.getFooterImage() != 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 = ins.left; if (tooltipInfo.getFooterImage() != null) { footerImageLabel = new JLabel(new ImageIcon(tooltipInfo .getFooterImage())); richTooltipPanel.add(footerImageLabel); footerImageLabel.setBounds(x, y, footerImageLabel .getPreferredSize().width, footerImageLabel .getPreferredSize().height); x += footerImageLabel.getWidth() + 2 * gap; } // The footer text int footerLabelWidth = parent.getWidth() - x - ins.right; 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); footerLabel.setBounds(x, y, footerLabel .getPreferredSize().width, fontHeight); y += footerLabel.getHeight(); currOffset += charCount; } // add an empty line after the paragraph y += titleLabel.getHeight(); } // remove the empty line after the last paragraph y -= titleLabel.getHeight(); } } } protected int getDescriptionTextWidth() { return 200; } protected int getLayoutGap() { return 4; } protected void removeExistingComponents() { if (this.titleLabel != null) { this.richTooltipPanel.remove(this.titleLabel); } 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); } }