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