// License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.gui.widgets; import java.awt.Color; import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Insets; import java.awt.RenderingHints; import java.awt.event.FocusEvent; import java.awt.event.FocusListener; import javax.swing.JTextField; import javax.swing.text.Document; import org.openstreetmap.josm.Main; /** * Subclass of {@link JTextField} that:<ul> * <li>adds a "native" context menu (undo/redo/cut/copy/paste/select all)</li> * <li>adds an optional "hint" displayed when no text has been entered</li> * <li>disables the global advanced key press detector when focused</li> * <li>implements a workaround to <a href="https://bugs.openjdk.java.net/browse/JDK-6322854">JDK bug 6322854</a></li> * </ul><br>This class must be used everywhere in core and plugins instead of {@code JTextField}. * @since 5886 */ public class JosmTextField extends JTextField implements FocusListener { private String hint; /** * Constructs a new <code>JosmTextField</code> that uses the given text * storage model and the given number of columns. * This is the constructor through which the other constructors feed. * If the document is <code>null</code>, a default model is created. * * @param doc the text storage to use; if this is <code>null</code>, * a default will be provided by calling the * <code>createDefaultModel</code> method * @param text the initial string to display, or <code>null</code> * @param columns the number of columns to use to calculate * the preferred width >= 0; if <code>columns</code> * is set to zero, the preferred width will be whatever * naturally results from the component implementation * @throws IllegalArgumentException if <code>columns</code> < 0 */ public JosmTextField(Document doc, String text, int columns) { this(doc, text, columns, true); } /** * Constructs a new <code>JosmTextField</code> that uses the given text * storage model and the given number of columns. * This is the constructor through which the other constructors feed. * If the document is <code>null</code>, a default model is created. * * @param doc the text storage to use; if this is <code>null</code>, * a default will be provided by calling the * <code>createDefaultModel</code> method * @param text the initial string to display, or <code>null</code> * @param columns the number of columns to use to calculate * the preferred width >= 0; if <code>columns</code> * is set to zero, the preferred width will be whatever * naturally results from the component implementation * @param undoRedo Enables or not Undo/Redo feature. Not recommended for table cell editors, unless each cell provides its own editor * @throws IllegalArgumentException if <code>columns</code> < 0 */ public JosmTextField(Document doc, String text, int columns, boolean undoRedo) { super(doc, text, columns); TextContextualPopupMenu.enableMenuFor(this, undoRedo); // Fix minimum size when columns are specified if (columns > 0) { setMinimumSize(getPreferredSize()); } addFocusListener(this); // Workaround for Java bug 6322854 JosmPasswordField.workaroundJdkBug6322854(this); } /** * Constructs a new <code>JosmTextField</code> initialized with the * specified text and columns. A default model is created. * * @param text the text to be displayed, or <code>null</code> * @param columns the number of columns to use to calculate * the preferred width; if columns is set to zero, the * preferred width will be whatever naturally results from * the component implementation */ public JosmTextField(String text, int columns) { this(null, text, columns); } /** * Constructs a new <code>JosmTextField</code> initialized with the * specified text. A default model is created and the number of * columns is 0. * * @param text the text to be displayed, or <code>null</code> */ public JosmTextField(String text) { this(null, text, 0); } /** * Constructs a new empty <code>JosmTextField</code> with the specified * number of columns. * A default model is created and the initial string is set to * <code>null</code>. * * @param columns the number of columns to use to calculate * the preferred width; if columns is set to zero, the * preferred width will be whatever naturally results from * the component implementation */ public JosmTextField(int columns) { this(null, null, columns); } /** * Constructs a new <code>JosmTextField</code>. A default model is created, * the initial string is <code>null</code>, * and the number of columns is set to 0. */ public JosmTextField() { this(null, null, 0); } /** * Replies the hint displayed when no text has been entered. * @return the hint * @since 7505 */ public final String getHint() { return hint; } /** * Sets the hint to display when no text has been entered. * @param hint the hint to set * @since 7505 */ public final void setHint(String hint) { this.hint = hint; } @Override public void paint(Graphics g) { super.paint(g); if (hint != null && !hint.isEmpty() && getText().isEmpty() && !isFocusOwner()) { // Taken from http://stackoverflow.com/a/24571681/2257172 int h = getHeight(); if (g instanceof Graphics2D) { ((Graphics2D) g).setRenderingHint( RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); } Insets ins = getInsets(); FontMetrics fm = g.getFontMetrics(); int c0 = getBackground().getRGB(); int c1 = getForeground().getRGB(); int m = 0xfefefefe; int c2 = ((c0 & m) >>> 1) + ((c1 & m) >>> 1); g.setColor(new Color(c2, true)); g.drawString(hint, ins.left, h / 2 + fm.getAscent() / 2 - 2); } } @Override public void focusGained(FocusEvent e) { if (Main.map != null) { Main.map.keyDetector.setEnabled(false); } repaint(); } @Override public void focusLost(FocusEvent e) { if (Main.map != null) { Main.map.keyDetector.setEnabled(true); } repaint(); } }