/*
 * @(#)SimpleStyleTreeCellRenderer.java
 *
 * Copyright 2002 - 2005 JIDE Software. All rights reserved.
 */

package com.jidesoft.tree;

import com.jidesoft.plaf.UIDefaultsLookup;
import com.jidesoft.swing.StyledLabel;

import javax.swing.*;
import javax.swing.plaf.ColorUIResource;
import javax.swing.plaf.FontUIResource;
import javax.swing.plaf.basic.BasicGraphicsUtils;
import javax.swing.tree.TreeCellRenderer;
import java.awt.*;

/**
 * A tree cell renderer based on StyledLabel. To use it, you should make your cell renderer extending this one and
 * override {@link #customizeStyledLabel(javax.swing.JTree, Object, boolean, boolean, boolean, int, boolean)} method. If your
 * overridden method, you can call setStyleRange() or setStyleRanges() based on the tree node value and row index.
 */
public class StyledTreeCellRenderer extends StyledLabel implements TreeCellRenderer {
    /**
     * Last tree the renderer was painted in.
     */
    private JTree tree;

    /**
     * Is the value currently selected.
     */
    protected boolean selected;
    /**
     * True if has focus.
     */
    protected boolean hasFocus;
    /**
     * True if draws focus border around icon as well.
     */
    private boolean drawsFocusBorderAroundIcon;
    /**
     * If true, a dashed line is drawn as the focus indicator.
     */
    private boolean drawDashedFocusIndicator;

    // If drawDashedFocusIndicator is true, the following are used.
    /**
     * Background color of the tree.
     */
    private Color treeBGColor;
    /**
     * Color to draw the focus indicator in, determined from the background. color.
     */
    private Color focusBGColor;

    // Icons
    /**
     * Icon used to show non-leaf nodes that aren't expanded.
     */
    transient protected Icon closedIcon;

    /**
     * Icon used to show leaf nodes.
     */
    transient protected Icon leafIcon;

    /**
     * Icon used to show non-leaf nodes that are expanded.
     */
    transient protected Icon openIcon;

    // Colors
    /**
     * Color to use for the foreground for selected nodes.
     */
    protected Color textSelectionColor;

    /**
     * Color to use for the foreground for non-selected nodes.
     */
    protected Color textNonSelectionColor;

    /**
     * Color to use for the background when a node is selected.
     */
    protected Color backgroundSelectionColor;

    /**
     * Color to use for the background when the node isn't selected.
     */
    protected Color backgroundNonSelectionColor;

    /**
     * Color to use for the focus indicator when the node has focus.
     */
    protected Color borderSelectionColor;

    /**
     * Returns a new instance of DefaultTreeCellRenderer.  Alignment is set to left aligned. Icons and text color are
     * determined from the UIManager.
     */
    public StyledTreeCellRenderer() {
        updateUIDefaults();
    }

    @Override
    public void updateUI() {
        super.updateUI();
        updateUIDefaults();
    }

    private void updateUIDefaults() {
        setLeafIcon(UIDefaultsLookup.getIcon("Tree.leafIcon"));
        setClosedIcon(UIDefaultsLookup.getIcon("Tree.closedIcon"));
        setOpenIcon(UIDefaultsLookup.getIcon("Tree.openIcon"));

        setTextSelectionColor(UIDefaultsLookup.getColor("Tree.selectionForeground"));
        setTextNonSelectionColor(UIDefaultsLookup.getColor("Tree.textForeground"));
        setBackgroundSelectionColor(UIDefaultsLookup.getColor("Tree.selectionBackground"));
        setBackgroundNonSelectionColor(UIDefaultsLookup.getColor("Tree.textBackground"));
        setBorderSelectionColor(UIDefaultsLookup.getColor("Tree.selectionBorderColor"));
        Object value = UIDefaultsLookup.get("Tree.drawsFocusBorderAroundIcon");
        drawsFocusBorderAroundIcon = (value != null && (Boolean) value);
        value = UIDefaultsLookup.get("Tree.drawDashedFocusIndicator");
        drawDashedFocusIndicator = (value != null && (Boolean) value);
    }


    /**
     * Returns the default icon, for the current laf, that is used to represent non-leaf nodes that are expanded.
     */
    public Icon getDefaultOpenIcon() {
        return UIDefaultsLookup.getIcon("Tree.openIcon");
    }

    /**
     * Returns the default icon, for the current laf, that is used to represent non-leaf nodes that are not expanded.
     */
    public Icon getDefaultClosedIcon() {
        return UIDefaultsLookup.getIcon("Tree.closedIcon");
    }

    /**
     * Returns the default icon, for the current laf, that is used to represent leaf nodes.
     */
    public Icon getDefaultLeafIcon() {
        return UIDefaultsLookup.getIcon("Tree.leafIcon");
    }

    /**
     * Sets the icon used to represent non-leaf nodes that are expanded.
     */
    public void setOpenIcon(Icon newIcon) {
        openIcon = newIcon;
    }

    /**
     * Returns the icon used to represent non-leaf nodes that are expanded.
     */
    public Icon getOpenIcon() {
        return openIcon;
    }

    /**
     * Sets the icon used to represent non-leaf nodes that are not expanded.
     */
    public void setClosedIcon(Icon newIcon) {
        closedIcon = newIcon;
    }

    /**
     * Returns the icon used to represent non-leaf nodes that are not expanded.
     */
    public Icon getClosedIcon() {
        return closedIcon;
    }

    /**
     * Sets the icon used to represent leaf nodes.
     */
    public void setLeafIcon(Icon newIcon) {
        leafIcon = newIcon;
    }

    /**
     * Returns the icon used to represent leaf nodes.
     */
    public Icon getLeafIcon() {
        return leafIcon;
    }

    /**
     * Sets the color the text is drawn with when the node is selected.
     */
    public void setTextSelectionColor(Color newColor) {
        textSelectionColor = newColor;
    }

    /**
     * Returns the color the text is drawn with when the node is selected.
     */
    public Color getTextSelectionColor() {
        return textSelectionColor;
    }

    /**
     * Sets the color the text is drawn with when the node isn't selected.
     */
    public void setTextNonSelectionColor(Color newColor) {
        textNonSelectionColor = newColor;
    }

    /**
     * Returns the color the text is drawn with when the node isn't selected.
     */
    public Color getTextNonSelectionColor() {
        return textNonSelectionColor;
    }

    /**
     * Sets the color to use for the background if node is selected.
     */
    public void setBackgroundSelectionColor(Color newColor) {
        backgroundSelectionColor = newColor;
    }


    /**
     * Returns the color to use for the background if node is selected.
     */
    public Color getBackgroundSelectionColor() {
        return backgroundSelectionColor;
    }

    /**
     * Sets the background color to be used for non selected nodes.
     */
    public void setBackgroundNonSelectionColor(Color newColor) {
        backgroundNonSelectionColor = newColor;
    }

    /**
     * Returns the background color to be used for non selected nodes.
     */
    public Color getBackgroundNonSelectionColor() {
        return backgroundNonSelectionColor;
    }

    /**
     * Sets the color to use for the border.
     */
    public void setBorderSelectionColor(Color newColor) {
        borderSelectionColor = newColor;
    }

    /**
     * Returns the color the border is drawn.
     */
    public Color getBorderSelectionColor() {
        return borderSelectionColor;
    }

    /**
     * Subclassed to map <code>FontUIResource</code>s to null. If <code>font</code> is null, or a
     * <code>FontUIResource</code>, this has the effect of letting the font of the JTree show through. On the other
     * hand, if <code>font</code> is non-null, and not a <code>FontUIResource</code>, the font becomes
     * <code>font</code>.
     */
    @Override
    public void setFont(Font font) {
        if (font instanceof FontUIResource)
            font = null;
        super.setFont(font);
    }

    /**
     * Gets the font of this component.
     *
     * @return this component's font; if a font has not been set for this component, the font of its parent is returned
     */
    @Override
    public Font getFont() {
        Font font = super.getFont();

        if (font == null && tree != null) {
            // Strive to return a non-null value, otherwise the html support
            // will typically pick up the wrong font in certain situations.
            font = tree.getFont();
        }
        return font;
    }

    /**
     * Subclassed to map <code>ColorUIResource</code>s to null. If <code>color</code> is null, or a
     * <code>ColorUIResource</code>, this has the effect of letting the background color of the JTree show through. On
     * the other hand, if <code>color</code> is non-null, and not a <code>ColorUIResource</code>, the background becomes
     * <code>color</code>.
     */
    @Override
    public void setBackground(Color color) {
        if (color instanceof ColorUIResource)
            color = null;
        super.setBackground(color);
    }

    /**
     * Configures the renderer based on the passed in components. The value is set from messaging the tree with
     * <code>convertValueToText</code>, which ultimately invokes <code>toString</code> on <code>value</code>. The
     * foreground color is set based on the selection and the icon is set based on the <code>leaf</code> and
     * <code>expanded</code> parameters.
     */
    public Component getTreeCellRendererComponent(JTree tree, Object value,
                                                  boolean sel,
                                                  boolean expanded,
                                                  boolean leaf, int row,
                                                  boolean hasFocus) {
        setOpaque(false);
        // There needs to be a way to specify disabled icons.
        if (!tree.isEnabled()) {
            setEnabled(false);
            if (leaf) {
                setDisabledIcon(getLeafIcon());
            }
            else if (expanded) {
                setDisabledIcon(getOpenIcon());
            }
            else {
                setDisabledIcon(getClosedIcon());
            }
        }
        else {
            setEnabled(true);
            if (leaf) {
                setIcon(getLeafIcon());
            }
            else if (expanded) {
                setIcon(getOpenIcon());
            }
            else {
                setIcon(getClosedIcon());
            }
        }

        setIgnoreColorSettings(sel);
        customizeStyledLabel(tree, value, sel, expanded, leaf, row, hasFocus);

        this.tree = tree;
        this.hasFocus = hasFocus;
        if (sel)
            setForeground(getTextSelectionColor());
        else
            setForeground(getTextNonSelectionColor());

        applyComponentOrientation(tree.getComponentOrientation());

        selected = sel;

        return this;
    }

    /**
     * Overrides this method to customize the styled label.
     *
     * @param tree
     * @param value
     * @param sel
     * @param expanded
     * @param leaf
     * @param row
     * @param hasFocus
     */
    protected void customizeStyledLabel(JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus) {
        String stringValue = tree.convertValueToText(value, sel,
                expanded, leaf, row, hasFocus);
        clearStyleRanges();
        setText(stringValue);
    }

    /**
     * Paints the value.  The background is filled based on selected.
     */
    @Override
    public void paint(Graphics g) {
        Color bColor;

        if (selected) {
            bColor = getBackgroundSelectionColor();
        }
        else {
            bColor = getBackgroundNonSelectionColor();
            if (bColor == null)
                bColor = getBackground();
        }
        int imageOffset = -1;

        if (selected || isOpaque()) {
            if (bColor != null) {
                imageOffset = getLabelStart();
                g.setColor(bColor);
                if (getComponentOrientation().isLeftToRight()) {
                    g.fillRect(imageOffset, 0, getWidth() - imageOffset, getHeight());
                }
                else {
                    g.fillRect(0, 0, getWidth() - imageOffset, getHeight());
                }
            }
        }

        super.paint(g);

        if (hasFocus) {
            if (drawsFocusBorderAroundIcon) {
                imageOffset = 0;
            }
            else if (imageOffset == -1) {
                imageOffset = getLabelStart();
            }
            if (getComponentOrientation().isLeftToRight()) {
                paintFocus(g, imageOffset, 0, getWidth() - imageOffset,
                        getHeight());
            }
            else {
                paintFocus(g, 0, 0, getWidth() - imageOffset, getHeight());
            }
        }
    }

    private void paintFocus(Graphics g, int x, int y, int w, int h) {
        Color bsColor = getBorderSelectionColor();

        if (bsColor != null && (selected || !drawDashedFocusIndicator)) {
            g.setColor(bsColor);
            g.drawRect(x, y, w - 1, h - 1);
        }
        if (drawDashedFocusIndicator) {
            Color color;
            if (selected) {
                color = getBackgroundSelectionColor();
            }
            else {
                color = getBackgroundNonSelectionColor();
                if (color == null) {
                    color = getBackground();
                }
            }

            if (treeBGColor != color) {
                treeBGColor = color;
                focusBGColor = new Color(~color.getRGB());
            }
            g.setColor(focusBGColor);
            BasicGraphicsUtils.drawDashedRect(g, x, y, w, h);
        }
    }

    private int getLabelStart() {
        Icon icon = getIcon();
        if (icon != null && getText().trim().length() != 0) {
            return icon.getIconWidth() + Math.max(0, getIconTextGap());
        }
        return 0;
    }

    /**
     * Overrides <code>JComponent.getPreferredSize</code> to return slightly wider preferred size value.
     */
    @Override
    public Dimension getPreferredSize() {
        Dimension retDimension = super.getPreferredSize();

        if (retDimension != null)
            retDimension = new Dimension(retDimension.width + 3,
                    retDimension.height);
        return retDimension;
    }

    /**
     * Overridden for performance reasons. See the <a href="#override">Implementation Note</a> for more information.
     */
    @Override
    public void validate() {
    }

    /**
     * Overridden for performance reasons. See the <a href="#override">Implementation Note</a> for more information.
     *
     * @since 1.5
     */
    @Override
    public void invalidate() {
    }

    /**
     * Overridden for performance reasons. See the <a href="#override">Implementation Note</a> for more information.
     */
    @Override
    public void revalidate() {
    }

    /**
     * Overridden for performance reasons. See the <a href="#override">Implementation Note</a> for more information.
     */
    @Override
    public void repaint(long tm, int x, int y, int width, int height) {
    }

    /**
     * Overridden for performance reasons. See the <a href="#override">Implementation Note</a> for more information.
     */
    @Override
    public void repaint(Rectangle r) {
    }

    /**
     * Overridden for performance reasons. See the <a href="#override">Implementation Note</a> for more information.
     *
     * @since 1.5
     */
    @Override
    public void repaint() {
    }

    /**
     * Overridden for performance reasons. See the <a href="#override">Implementation Note</a> for more information.
     */
    @Override
    protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) {
        // Strings get interned...
        if (propertyName.equals("text"))
            super.firePropertyChange(propertyName, oldValue, newValue);
    }

    /**
     * Overridden for performance reasons. See the <a href="#override">Implementation Note</a> for more information.
     */
    @Override
    public void firePropertyChange(String propertyName, byte oldValue, byte newValue) {
    }

    /**
     * Overridden for performance reasons. See the <a href="#override">Implementation Note</a> for more information.
     */
    @Override
    public void firePropertyChange(String propertyName, char oldValue, char newValue) {
    }

    /**
     * Overridden for performance reasons. See the <a href="#override">Implementation Note</a> for more information.
     */
    @Override
    public void firePropertyChange(String propertyName, short oldValue, short newValue) {
    }

    /**
     * Overridden for performance reasons. See the <a href="#override">Implementation Note</a> for more information.
     */
    @Override
    public void firePropertyChange(String propertyName, int oldValue, int newValue) {
    }

    /**
     * Overridden for performance reasons. See the <a href="#override">Implementation Note</a> for more information.
     */
    @Override
    public void firePropertyChange(String propertyName, long oldValue, long newValue) {
    }

    /**
     * Overridden for performance reasons. See the <a href="#override">Implementation Note</a> for more information.
     */
    @Override
    public void firePropertyChange(String propertyName, float oldValue, float newValue) {
    }

    /**
     * Overridden for performance reasons. See the <a href="#override">Implementation Note</a> for more information.
     */
    @Override
    public void firePropertyChange(String propertyName, double oldValue, double newValue) {
    }

    /**
     * Overridden for performance reasons. See the <a href="#override">Implementation Note</a> for more information.
     */
    @Override
    public void firePropertyChange(String propertyName, boolean oldValue, boolean newValue) {
    }

}
