001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui;
003
004import java.awt.BorderLayout;
005import java.awt.Color;
006import java.awt.Image;
007import java.awt.Insets;
008import java.awt.event.ActionListener;
009import java.beans.PropertyChangeEvent;
010import java.beans.PropertyChangeListener;
011
012import javax.swing.Action;
013import javax.swing.BorderFactory;
014import javax.swing.Icon;
015import javax.swing.ImageIcon;
016import javax.swing.JButton;
017import javax.swing.SwingConstants;
018import javax.swing.plaf.basic.BasicArrowButton;
019
020import org.openstreetmap.josm.Main;
021import org.openstreetmap.josm.tools.Destroyable;
022import org.openstreetmap.josm.tools.ImageProvider;
023import org.openstreetmap.josm.tools.ImageResource;
024
025/**
026 * Button that is usually used in toggle dialogs.
027 * @since 744
028 */
029public class SideButton extends JButton implements Destroyable {
030
031    private transient PropertyChangeListener propertyChangeListener;
032
033    /**
034     * Constructs a new {@code SideButton}.
035     * @param action action used to specify the new button
036     * @since 744
037     */
038    public SideButton(Action action) {
039        super(action);
040        ImageResource icon = (ImageResource) action.getValue("ImageResource");
041        if (icon != null) {
042            setIcon(icon.getImageIconBounded(
043                ImageProvider.ImageSizes.SIDEBUTTON.getImageDimension()));
044        } else if (getIcon() != null) { /* TODO: remove when calling code is fixed, replace by exception */
045            Main.warn("Old style SideButton usage for action " + action);
046            fixIcon(action);
047        }
048        doStyle();
049    }
050
051    /**
052     * Constructs a new {@code SideButton}.
053     * @param action action used to specify the new button
054     * @param usename use action name
055     * @since 2710
056     */
057    public SideButton(Action action, boolean usename) {
058        this(action);
059        if (!usename) {
060            setText(null);
061        }
062    }
063
064    /**
065     * Constructs a new {@code SideButton}.
066     * @param action action used to specify the new button
067     * @param imagename image name in "dialogs" directory
068     * @since 2747
069     */
070    public SideButton(Action action, String imagename) {
071        super(action);
072        setIcon(ImageProvider.get("dialogs", imagename, ImageProvider.ImageSizes.SIDEBUTTON));
073        doStyle();
074    }
075
076    /**
077     * Fix icon size
078     * @param action the action
079     * @deprecated This method is old style and will be removed together with the removal
080     * of old constructor code
081     */
082    @Deprecated
083    private void fixIcon(Action action) {
084        // need to listen for changes, so that putValue() that are called after the
085        // SideButton is constructed get the proper icon size
086        if (action != null) {
087            propertyChangeListener = new PropertyChangeListener() {
088                @Override
089                public void propertyChange(PropertyChangeEvent evt) {
090                    if (javax.swing.Action.SMALL_ICON.equals(evt.getPropertyName())) {
091                        fixIcon(null);
092                    }
093                }
094            };
095            action.addPropertyChangeListener(propertyChangeListener);
096        }
097        int iconHeight = ImageProvider.ImageSizes.SIDEBUTTON.getImageDimension().height;
098        Icon i = getIcon();
099        if (i instanceof ImageIcon && i.getIconHeight() != iconHeight) {
100            Image im = ((ImageIcon) i).getImage();
101            int newWidth = im.getWidth(null) * iconHeight / im.getHeight(null);
102            ImageIcon icon = new ImageIcon(im.getScaledInstance(newWidth, iconHeight, Image.SCALE_SMOOTH));
103            setIcon(icon);
104        }
105    }
106
107    /**
108     * Do the style settings for the side button layout
109     */
110    private void doStyle() {
111        setLayout(new BorderLayout());
112        setIconTextGap(2);
113        setMargin(new Insets(0, 0, 0, 0));
114    }
115
116    /**
117     * Create the arrow for opening a drop-down menu
118     * @param listener listener to use for button actions (e.g. pressing)
119     * @return the created button
120     * @since 9668
121     */
122    public BasicArrowButton createArrow(ActionListener listener) {
123        setMargin(new Insets(0, 0, 0, 0));
124        BasicArrowButton arrowButton = new BasicArrowButton(SwingConstants.SOUTH, null, null, Color.BLACK, null);
125        arrowButton.setBorder(BorderFactory.createEmptyBorder());
126        add(arrowButton, BorderLayout.EAST);
127        arrowButton.addActionListener(listener);
128        return arrowButton;
129    }
130
131    @Override
132    public void destroy() {
133        Action action = getAction();
134        if (action instanceof Destroyable) {
135            ((Destroyable) action).destroy();
136        }
137        if (action != null) {
138            if (propertyChangeListener != null) {
139                action.removePropertyChangeListener(propertyChangeListener);
140            }
141            setAction(null);
142        }
143    }
144}