ColorControlPanel.java

package org.opentrafficsim.swing.gui;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.function.Predicate;

import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.border.Border;
import javax.swing.border.LineBorder;

import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.title.PaintScaleLegend;
import org.jfree.chart.ui.RectangleInsets;
import org.opentrafficsim.animation.gtu.colorer.GtuColorerManager;
import org.opentrafficsim.animation.gtu.colorer.GtuColorerManager.PredicatedColorer;
import org.opentrafficsim.core.gtu.Gtu;
import org.opentrafficsim.draw.colorer.ColorbarColorer;
import org.opentrafficsim.draw.colorer.Colorer;
import org.opentrafficsim.draw.colorer.LegendColorer;
import org.opentrafficsim.draw.colorer.LegendColorer.LegendEntry;

/**
 * Let the user select what the colors in the animation mean.
 * <p>
 * Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
 * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
 * </p>
 * @author <a href="https://github.com/peter-knoppers">Peter Knoppers</a>
 */
public class ColorControlPanel extends JPanel implements ActionListener, AppearanceControl
{
    /** */
    private static final long serialVersionUID = 20150527L;

    /** The combo box that sets the coloring for the GTUs. */
    private final JComboBox<PredicatedColorer> comboBoxGTUColor = new AppearanceControlComboBox<>();

    /** The panel that holds the legend for the currently selected GtuColorer. */
    private final JPanel legendPanel;

    /** GTU colorer manager. */
    private final GtuColorerManager gtuColorerManager = new GtuColorerManager(Color.WHITE);

    /** The GtuColorer that is currently active. */
    private Colorer<? super Gtu> gtuColorer;

    /**
     * Add a ColorControlPanel to an AnimationPanel. Initially the ColorControlPanel will have no items. Items are added with
     * the <code>addItem</code> method.
     */
    public ColorControlPanel()
    {
        this.setLayout(new FlowLayout(FlowLayout.LEFT));
        this.legendPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 5, 0));
        this.add(this.comboBoxGTUColor);
        this.add(this.legendPanel);
        this.comboBoxGTUColor.addActionListener(this);
    }

    /**
     * Add one item to this ColorControlPanel.
     * @param colorer the GtuColorer that will be added
     */
    public final void addItem(final Colorer<? super Gtu> colorer)
    {
        if (this.gtuColorer == null)
        {
            this.gtuColorer = colorer;
        }
        Predicate<Gtu> predicate = (gtu) -> colorer.equals(ColorControlPanel.this.gtuColorer);
        this.comboBoxGTUColor.addItem(new PredicatedColorer(predicate, colorer));
        this.gtuColorerManager.add(predicate, colorer);
        rebuildLegend();
    }

    /**
     * Returns the GTU colorer manager.
     * @return GTU colorer manager
     */
    public GtuColorerManager getGtuColorerManager()
    {
        return this.gtuColorerManager;
    }

    @Override
    public final void actionPerformed(final ActionEvent e)
    {
        PredicatedColorer newColorerWrapper = (PredicatedColorer) this.comboBoxGTUColor.getSelectedItem();
        if (null != newColorerWrapper)
        {
            this.gtuColorer = newColorerWrapper.colorer();
            rebuildLegend();
        }
    }

    /**
     * Build or rebuild the legend on the screen.
     */
    private void rebuildLegend()
    {
        this.legendPanel.removeAll();
        if (this.gtuColorer instanceof ColorbarColorer<?> colorbarColorer)
        {
            NumberAxis scaleAxis = new NumberAxis("");
            scaleAxis.setNumberFormatOverride(colorbarColorer.getNumberFormat());
            // reduce tick insets from [t=2.0,l=4.0,b=2.0,r=4.0] to cramp legend in small ColorControlPanel
            scaleAxis.setTickLabelInsets(new RectangleInsets(0.0, 4.0, 0.0, 4.0));
            scaleAxis.setTickLabelPaint(getForeground());
            PaintScaleLegend colorbar = new PaintScaleLegend(colorbarColorer.getBoundsPaintScale(), scaleAxis);
            // 13px is all that fits without increasing the height of the ColorControlPanel, 1px margin for other computers
            colorbar.setStripWidth(12.0);
            colorbar.setSubdivisionCount(256);
            colorbar.setPadding(0.0, 25.0, 0.0, 25.0); // horizontal padding for numbers (and units) centered at extreme ticks
            colorbar.setBackgroundPaint(new Color(0, 0, 0, 0));
            JPanel panel = new JPanel()
            {
                private static final long serialVersionUID = 1L;

                @Override
                public void paint(final Graphics g)
                {
                    colorbar.draw((Graphics2D) g, getBounds());
                }
            };
            panel.setPreferredSize(new Dimension(400, 26));
            this.legendPanel.add(panel);
        }
        else if (this.gtuColorer instanceof LegendColorer<?> legendColorer)
        {
            for (LegendEntry legendEntry : legendColorer.getLegend())
            {
                JPanel panel = new JPanel(new BorderLayout());
                /**
                 * ColorBox for AppearanceControl.
                 */
                class ColorBox extends JLabel implements AppearanceControl
                {
                    /** */
                    private static final long serialVersionUID = 20180206L;

                    /**
                     * Constructor.
                     */
                    ColorBox()
                    {
                        super("     ");
                    }

                    @Override
                    public String toString()
                    {
                        return "ColorBox []";
                    }

                }
                ColorBox colorBox = new ColorBox();
                colorBox.setOpaque(true); // By default, the label is transparent
                colorBox.setBackground(legendEntry.color());
                Border border = LineBorder.createBlackLineBorder();
                colorBox.setBorder(border);
                panel.add(colorBox, BorderLayout.LINE_START);
                JLabel name = new JLabel(" " + legendEntry.name().trim());
                panel.add(name, BorderLayout.CENTER);
                name.setOpaque(true);
                name.setForeground(getForeground());
                name.setBackground(getBackground());
                panel.setToolTipText(legendEntry.description());
                this.legendPanel.add(panel);
            }
        }
        this.legendPanel.revalidate();

        Container parentPanel = this.getParent();
        if (parentPanel != null)
        {
            parentPanel.getParent().repaint();
        }
    }

    /** {@inheritDoc} */
    @Override
    public boolean isBackground()
    {
        return true;
    }

    /** {@inheritDoc} */
    @Override
    public boolean isForeground()
    {
        return true;
    }

    /** {@inheritDoc} */
    @Override
    public void setForeground(final Color fg)
    {
        super.setForeground(fg);
        if (this.legendPanel != null)
        {
            // update tick label color which is based on the foreground color
            rebuildLegend();
        }
    }

    @Override
    public final String toString()
    {
        return "ColorControlPanel [gtuColorer=" + this.gtuColorer + "]";
    }

}