001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.widgets;
003
004import java.awt.event.ActionEvent;
005import java.awt.event.FocusEvent;
006import java.awt.event.FocusListener;
007import java.beans.PropertyChangeListener;
008
009import javax.swing.Action;
010import javax.swing.JPasswordField;
011import javax.swing.TransferHandler;
012import javax.swing.text.Document;
013import javax.swing.text.JTextComponent;
014
015import org.openstreetmap.josm.Main;
016
017/**
018 * A subclass of {@link JPasswordField} to implement a workaround to
019 * <a href="https://bugs.openjdk.java.net/browse/JDK-6322854">JDK bug 6322854</a>.
020 *
021 * @since 5752
022 * @see <a href="https://josm.openstreetmap.de/ticket/8404">https://josm.openstreetmap.de/ticket/8404</a>
023 * @see <a href="https://hg.netbeans.org/main/rev/33cb2e81b640">https://hg.netbeans.org/main/rev/33cb2e81b640</a>
024 */
025public class JosmPasswordField extends JPasswordField implements FocusListener {
026
027    /**
028     * Constructs a new <code>JosmPasswordField</code>,
029     * with a default document, <code>null</code> starting
030     * text string, and 0 column width.
031     */
032    public JosmPasswordField() {
033        workaroundJdkBug6322854(this);
034        addFocusListener(this);
035    }
036
037    /**
038     * Constructs a new <code>JosmPasswordField</code> that uses the
039     * given text storage model and the given number of columns.
040     * This is the constructor through which the other constructors feed.
041     * The echo character is set to '*', but may be changed by the current
042     * Look and Feel.  If the document model is
043     * <code>null</code>, a default one will be created.
044     *
045     * @param doc  the text storage to use
046     * @param txt the text to be displayed, <code>null</code> if none
047     * @param columns  the number of columns to use to calculate
048     *   the preferred width &gt;= 0; if columns is set to zero, the
049     *   preferred width will be whatever naturally results from
050     *   the component implementation
051     */
052    public JosmPasswordField(Document doc, String txt, int columns) {
053        super(doc, txt, columns);
054        workaroundJdkBug6322854(this);
055        addFocusListener(this);
056    }
057
058    /**
059     * Constructs a new empty <code>JosmPasswordField</code> with the specified
060     * number of columns.  A default model is created, and the initial string
061     * is set to <code>null</code>.
062     *
063     * @param columns the number of columns &gt;= 0
064     */
065    public JosmPasswordField(int columns) {
066        super(columns);
067        workaroundJdkBug6322854(this);
068        addFocusListener(this);
069    }
070
071    /**
072     * Constructs a new <code>JPasswordField</code> initialized with
073     * the specified text and columns.  The document model is set to
074     * the default.
075     *
076     * @param text the text to be displayed, <code>null</code> if none
077     * @param columns the number of columns &gt;= 0
078     */
079    public JosmPasswordField(String text, int columns) {
080        super(text, columns);
081        workaroundJdkBug6322854(this);
082        addFocusListener(this);
083    }
084
085    /**
086     * Constructs a new <code>JosmPasswordField</code> initialized
087     * with the specified text.  The document model is set to the
088     * default, and the number of columns to 0.
089     *
090     * @param text the text to be displayed, <code>null</code> if none
091     */
092    public JosmPasswordField(String text) {
093        super(text);
094        workaroundJdkBug6322854(this);
095        addFocusListener(this);
096    }
097
098    @Override
099    public void focusGained(FocusEvent e) {
100        if (Main.map != null) {
101            Main.map.keyDetector.setEnabled(false);
102        }
103    }
104
105    @Override
106    public void focusLost(FocusEvent e) {
107        if (Main.map != null) {
108            Main.map.keyDetector.setEnabled(true);
109        }
110    }
111
112    /**
113     * Implements a workaround to <a href="https://bugs.openjdk.java.net/browse/JDK-6322854">JDK bug 6322854</a>.
114     * This method can be deleted after Oracle decides to fix this bug...
115     * @param text The {@link JTextComponent} to protect.
116     */
117    public static final void workaroundJdkBug6322854(final JTextComponent text) {
118        if (text != null) {
119            text.getActionMap().put("paste", new Action() {
120
121                private final Action pasteAction = TransferHandler.getPasteAction();
122
123                @Override
124                public void actionPerformed(ActionEvent e) {
125                    try {
126                        pasteAction.actionPerformed(e);
127                    } catch (NullPointerException npe) {
128                        Main.error("NullPointerException occured because of JDK bug 6322854. "
129                                +"Copy/Paste operation has not been performed. Please complain to Oracle: "+
130                                "https://bugs.openjdk.java.net/browse/JDK-6322854");
131                    }
132                }
133
134                @Override
135                public void setEnabled(boolean b) {
136                    pasteAction.setEnabled(b);
137                }
138
139                @Override
140                public void removePropertyChangeListener(PropertyChangeListener listener) {
141                    pasteAction.removePropertyChangeListener(listener);
142                }
143
144                @Override
145                public void putValue(String key, Object value) {
146                    pasteAction.putValue(key, value);
147                }
148
149                @Override
150                public boolean isEnabled() {
151                    return pasteAction.isEnabled();
152                }
153
154                @Override
155                public Object getValue(String key) {
156                    return pasteAction.getValue(key);
157                }
158
159                @Override
160                public void addPropertyChangeListener(PropertyChangeListener listener) {
161                    pasteAction.addPropertyChangeListener(listener);
162                }
163            });
164        }
165    }
166}