/* * Copyright (c) 2007 BUSINESS OBJECTS SOFTWARE LIMITED * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * * 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. * * * Neither the name of Business Objects 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. */ /* * ExplorerCellRenderer.java * Created: Feb 13, 2004 * By: David Mosimann */ package org.openquark.gems.client.explorer; import java.awt.Color; import java.awt.Component; import java.awt.Font; import java.awt.Graphics; import java.awt.Image; import java.awt.Point; import java.awt.SystemColor; import java.awt.Toolkit; import java.awt.font.TextAttribute; import java.text.AttributedString; import java.util.HashMap; import java.util.Map; import javax.swing.ImageIcon; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JTree; import javax.swing.plaf.ComponentUI; import javax.swing.plaf.TreeUI; import javax.swing.plaf.basic.BasicLabelUI; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultTreeCellRenderer; import org.openquark.cal.compiler.ClassMethod; import org.openquark.cal.compiler.DataConstructor; import org.openquark.cal.compiler.Function; import org.openquark.cal.compiler.FunctionalAgent; import org.openquark.cal.compiler.ModuleTypeInfo; import org.openquark.cal.compiler.ScopedEntityNamingPolicy; import org.openquark.cal.compiler.ScopedEntityNamingPolicy.UnqualifiedUnlessAmbiguous; import org.openquark.cal.metadata.ArgumentMetadata; import org.openquark.cal.valuenode.ValueNode; import org.openquark.gems.client.CodeGem; import org.openquark.gems.client.CollectorGem; import org.openquark.gems.client.FunctionalAgentGem; import org.openquark.gems.client.Gem; import org.openquark.gems.client.RecordCreationGem; import org.openquark.gems.client.RecordFieldSelectionGem; import org.openquark.gems.client.ReflectorGem; import org.openquark.gems.client.ValueGem; /** * The renderer for the TableTopExplorer tree. * @author Ken Wong */ public class ExplorerCellRenderer extends DefaultTreeCellRenderer { private static final long serialVersionUID = 1131870254758332921L; /** The table top exlorer owner of the explorer tree. */ private final TableTopExplorerOwner tableTopExplorerOwner; /** Whether or not the tree is using the banded look and feel. */ private boolean useBandedLookAndFeel; /** Used to store if the current node is focusable or not. */ public boolean focusable; /* the various icons used in the tree */ private static final ImageIcon constructorIcon; private static final ImageIcon supercombinatorIcon; private static final ImageIcon codeGemIcon; private static final ImageIcon brokenCodeGemIcon; private static final ImageIcon collectorIcon; // private static final ImageIcon targetCollectorIcon; private static final ImageIcon emitterIcon; private static final ImageIcon minorTargetIcon; private static final ImageIcon reflectorIcon; private static final ImageIcon brokenReflectorGemIcon; private static final ImageIcon partInputIcon; private static final ImageIcon burnIcon; private static final ImageIcon valueIcon; private static final ImageIcon recordFieldSelectionIcon; private static final ImageIcon recordCreationIcon; static { // Make icon objects constructorIcon = new ImageIcon(Object.class.getResource("/Resources/dataConstructor.gif")); supercombinatorIcon = new ImageIcon(Object.class.getResource("/Resources/supercombinator.gif")); codeGemIcon = new ImageIcon(Object.class.getResource("/Resources/code.gif")); brokenCodeGemIcon = new ImageIcon(Object.class.getResource("/Resources/codeBroken.gif")); collectorIcon = new ImageIcon(Object.class.getResource("/Resources/collector.gif")); // targetCollectorIcon = new ImageIcon(Object.class.getResource("/Resources/targetCollector.gif")); emitterIcon = new ImageIcon(Object.class.getResource("/Resources/emitter.gif")); minorTargetIcon = new ImageIcon(Object.class.getResource("/Resources/targetbw.gif")); reflectorIcon = new ImageIcon(Object.class.getResource("/Resources/reflector.gif")); brokenReflectorGemIcon = new ImageIcon(Object.class.getResource("/Resources/reflectorBroken.gif")); partInputIcon = new ImageIcon(Object.class.getResource("/Resources/partinput.gif")); valueIcon = new ImageIcon(Object.class.getResource("/Resources/constant.gif")); Image burnedImage = Toolkit.getDefaultToolkit().createImage(Object.class.getResource("/Resources/burn.gif")).getScaledInstance(16,16,Image.SCALE_SMOOTH); burnIcon = new ImageIcon(burnedImage); recordFieldSelectionIcon = new ImageIcon(Object.class.getResource("/Resources/recordFieldSelectionGem.gif")); recordCreationIcon = new ImageIcon(Object.class.getResource("/Resources/recordCreationGem.gif")); } /** * Default Constructor for the cell renderer; * @param owner the explorer owner using the tree */ protected ExplorerCellRenderer(TableTopExplorerOwner owner) { this.tableTopExplorerOwner = owner; // Set the UI to use the hyperlink label UI setUI(HyperlinkLabelUI.createUI(this)); } public void setBandedLookAndFeel(boolean useBandedLookAndFeel) { this.useBandedLookAndFeel = useBandedLookAndFeel; } /** * Overide this method so that we can explicitly use a special banded look and feel even if a different * look and feel is requested. * @param newUI */ public void setUI(TreeUI newUI) { super.setUI(HyperlinkLabelUI.createUI(this)); } /** * @see javax.swing.tree.TreeCellRenderer#getTreeCellRendererComponent(javax.swing.JTree, java.lang.Object, boolean, boolean, boolean, int, boolean) */ @Override public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus) { // By default the node is not focussable focusable = false; if (tableTopExplorerOwner.hasPhotoLook()) { setTextNonSelectionColor(Color.BLACK); setTextSelectionColor(Color.WHITE); setBackgroundSelectionColor(Color.BLUE.darker().darker()); } else { setTextNonSelectionColor(SystemColor.textText); setTextSelectionColor(SystemColor.textHighlightText); setBackgroundSelectionColor(SystemColor.textHighlight); } setBackgroundNonSelectionColor(new Color(1, 1, 1, 1)); setFont(null); ImageIcon customIcon = null; String customText = value.toString(); // Now we 'tweak' the component a bit to get the look we want. if (value instanceof ExplorerGemNode) { ExplorerGemNode explorerNode = (ExplorerGemNode) value; Gem gem = explorerNode.getGem(); // Get the customized name and icon customIcon = getGemDisplayIcon(gem); customText = getGemDisplayString(gem); } else if (value instanceof DefaultMutableTreeNode) { // Other nodes in the trees are dealt with here DefaultMutableTreeNode treeNode = (DefaultMutableTreeNode) value; if (treeNode.isRoot()) { customIcon = ExplorerRootNode.rootNodeIcon; } else if (treeNode.getUserObject() instanceof Gem.PartInput) { Gem.PartInput input = (Gem.PartInput) treeNode.getUserObject(); ValueNode valueNode = tableTopExplorerOwner.getValueNode(input); ArgumentMetadata metadata = input.getDesignMetadata(); if (tableTopExplorerOwner.highlightInput(input)) { // TODO: instead of hardcoding the highlighted font style, perhaps the renderer // should be passed to the highlight fn and let it set whatever is appropriate... setTextNonSelectionColor(Color.RED); setFont(tree.getFont().deriveFont(Font.ITALIC)); } if (tableTopExplorerOwner.canEditInputsAsValues() && valueNode != null && !input.isBurnt()) { customText = input.getArgumentName().getCompositeName() + ": " + valueNode.getTextValue(); } else { customIcon = partInputIcon; if (input.isBurnt()) { setTextNonSelectionColor(Color.RED.darker()); setTextSelectionColor(Color.WHITE); setBackgroundSelectionColor(Color.RED.darker()); customIcon = burnIcon; } if (metadata.getShortDescription() != null) { customText = input.getArgumentName().getCompositeName() + " - " + metadata.getShortDescription(); } else { customText = input.getArgumentName().getCompositeName(); } } } } // If we are using the banded look and feel then we don't want the super class to draw focus // borders around the objects. They look funny with the banded background colour. if (useBandedLookAndFeel) { hasFocus = false; } super.getTreeCellRendererComponent(tree, customText, sel, expanded, leaf, row, hasFocus); if (customIcon != null) { setIcon(customIcon); } return this; } /** * Returns whether the current node should be rendered as a hyperlink. * @return Returns true is the current node is focusable and should be drawn as a hyperlink. */ boolean renderAsHyperlink() { return focusable; } /** * Determines if the point in question is over the icon portion of the rendered node. * @return whether the point in question is over the icon portion of the rendered node. */ boolean isPointOverIcon(Point p) { // Assume all icons are the same size and arbitrarily check the first icon return (p.x < constructorIcon.getIconWidth()); } /** * Determines the suitable display string for the gem and returns it. The default behaviour is to * use the unqualified gem name unless ambiguous. Different subclasses may have different behaviour * such as different naming policies or using portions of the metadata. * @param gem * @return A string that will be rendered as part of a gem node in the explorer tree */ protected String getGemDisplayString(Gem gem) { // different icons and text for each type of gem if (gem instanceof FunctionalAgentGem) { ModuleTypeInfo currentModuleTypeInfo = tableTopExplorerOwner.getCurrentModuleTypeInfo(); if (currentModuleTypeInfo == null) { return null; } ScopedEntityNamingPolicy namingPolicy = new UnqualifiedUnlessAmbiguous(currentModuleTypeInfo); FunctionalAgentGem functionalAgentGem = (FunctionalAgentGem) gem; return functionalAgentGem.getGemEntity().getAdaptedName(namingPolicy); } else if (gem instanceof CodeGem) { return ((CodeGem)gem).getUnqualifiedName(); } else if (gem instanceof ValueGem) { return ((ValueGem)gem).getTextValue(); } else if (gem instanceof CollectorGem) { // Start with the unqualified collector gem name return ((CollectorGem)gem).getUnqualifiedName(); } else if (gem instanceof ReflectorGem) { return ((ReflectorGem)gem).getUnqualifiedName(); } else if (gem instanceof RecordFieldSelectionGem) { return ((RecordFieldSelectionGem)gem).getDisplayedText(); } else if (gem instanceof RecordCreationGem) { return ((RecordCreationGem)gem).getDisplayName(); } else { return gem.toString(); } } /** * Determines the suitable icon for the gem and returns it. * @param gem * @return An icon that will be rendered as part of a gem node in the explorer tree */ protected ImageIcon getGemDisplayIcon(Gem gem) { // different icons for each type of gem if (gem instanceof FunctionalAgentGem) { FunctionalAgentGem functionalAgentGem = (FunctionalAgentGem) gem; FunctionalAgent functionalAgent = functionalAgentGem.getGemEntity().getFunctionalAgent(); if (functionalAgent instanceof Function || functionalAgent instanceof ClassMethod) { return supercombinatorIcon; } else if (functionalAgent instanceof DataConstructor) { return constructorIcon; } else { return null; } } else if (gem instanceof CodeGem) { return gem.isBroken() ? brokenCodeGemIcon : codeGemIcon; } else if (gem instanceof ValueGem) { return valueIcon; } else if (gem instanceof CollectorGem) { return ((CollectorGem)gem).getTargetCollector() == null ? minorTargetIcon : collectorIcon; } else if (gem instanceof ReflectorGem) { return gem.isBroken() ? brokenReflectorGemIcon : (gem.getNInputs() == 0) ? emitterIcon : reflectorIcon; } else if (gem instanceof RecordFieldSelectionGem) { return recordFieldSelectionIcon; } else if (gem instanceof RecordCreationGem) { return recordCreationIcon; } else { return null; } } } /** * A look and feel for painting JLabel text. If the label to be painted is an instance of a * HyperlinkedExplorerCellRenderer and it is set to render as a hyperlink then the painting will try * to mimic the default hyperlink style used in browsers (underlined with blue font colour). */ class HyperlinkLabelUI extends BasicLabelUI { /** A singleton object holding onto the UI that can be shared by all label components. */ private final static HyperlinkLabelUI hyperlinkLabelUI = new HyperlinkLabelUI(); /** * Static method to fetch the UI * @param c The component that we are fetching UI for. This is ignored, but assumed to be an * instance of a JLabel * @return ComponentUI returns the singleton object for this class. */ public static ComponentUI createUI(JComponent c){ return hyperlinkLabelUI; } /** * If the label is an instance of HyperlinkedExplorerCellRenderer and it is set to render as a * hyperlink then the font colour is changed to blue and the text is underlined. Otherwise the * text is painted as ordinary text. * @param l The label that this UI is associated with * @param g The graphics used for painting * @param s The string that should be painted * @param textX The X offset to start painting at * @param textY the Y offset to start painting at */ private void paintHyperlinkedText(JLabel l, Graphics g, String s, int textX, int textY) { // The label should actually be a HyperlinkedExplorerCellRenderer if (l instanceof ExplorerCellRenderer) { ExplorerCellRenderer renderer = (ExplorerCellRenderer)l; if (renderer.renderAsHyperlink()) { // Create an attributed string so that the characters are underlined and blue Map<TextAttribute, Object> attrs = new HashMap<TextAttribute, Object>(); attrs.put(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_LOW_ONE_PIXEL); attrs.put(TextAttribute.FONT, renderer.getFont()); // If the label is using the selected colour then render the hyperlink in white text // since it will look a lot better than blue given the default dark blue selection colour. // Otherwise the text should be rendered blue like a normal hyperlink. if (renderer.getForeground() == renderer.getTextSelectionColor()) { attrs.put(TextAttribute.FOREGROUND, Color.WHITE); } else { attrs.put(TextAttribute.FOREGROUND, Color.BLUE); } AttributedString attrString = new AttributedString(s, attrs); // Draw the 'hyperlink' string g.drawString(attrString.getIterator(), textX, textY); return; } } // If this is not the right renderer or not a hyperlink then simply draw the plain text g.drawString(s, textX, textY); } /** * Paints the text for an enabled node. * @param l The label that this UI is associated with * @param g The graphics used for painting * @param s The string that should be painted * @param textX The X offset to start painting at * @param textY the Y offset to start painting at */ @Override protected void paintEnabledText(JLabel l, Graphics g, String s, int textX, int textY) { paintHyperlinkedText(l, g, s, textX, textY); } /** * Paints the text for a disabled node. This currently paints the node exactly the same as an * enabled node. * @param l The label that this UI is associated with * @param g The graphics used for painting * @param s The string that should be painted * @param textX The X offset to start painting at * @param textY the Y offset to start painting at */ @Override protected void paintDisabledText(JLabel l, Graphics g, String s, int textX, int textY) { paintHyperlinkedText(l, g, s, textX, textY); } }