001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.widgets; 003 004import java.awt.Color; 005import java.awt.FontMetrics; 006import java.awt.Graphics; 007import java.awt.Graphics2D; 008import java.awt.Insets; 009import java.awt.RenderingHints; 010import java.awt.event.FocusEvent; 011import java.awt.event.FocusListener; 012 013import javax.swing.JTextField; 014import javax.swing.text.Document; 015 016import org.openstreetmap.josm.Main; 017 018/** 019 * Subclass of {@link JTextField} that:<ul> 020 * <li>adds a "native" context menu (undo/redo/cut/copy/paste/select all)</li> 021 * <li>adds an optional "hint" displayed when no text has been entered</li> 022 * <li>disables the global advanced key press detector when focused</li> 023 * <li>implements a workaround to <a href="https://bugs.openjdk.java.net/browse/JDK-6322854">JDK bug 6322854</a></li> 024 * <br>This class must be used everywhere in core and plugins instead of {@code JTextField}. 025 * @since 5886 026 */ 027public class JosmTextField extends JTextField implements FocusListener { 028 029 private String hint; 030 031 /** 032 * Constructs a new <code>JosmTextField</code> that uses the given text 033 * storage model and the given number of columns. 034 * This is the constructor through which the other constructors feed. 035 * If the document is <code>null</code>, a default model is created. 036 * 037 * @param doc the text storage to use; if this is <code>null</code>, 038 * a default will be provided by calling the 039 * <code>createDefaultModel</code> method 040 * @param text the initial string to display, or <code>null</code> 041 * @param columns the number of columns to use to calculate 042 * the preferred width >= 0; if <code>columns</code> 043 * is set to zero, the preferred width will be whatever 044 * naturally results from the component implementation 045 * @exception IllegalArgumentException if <code>columns</code> < 0 046 */ 047 public JosmTextField(Document doc, String text, int columns) { 048 this(doc, text, columns, true); 049 } 050 051 /** 052 * Constructs a new <code>JosmTextField</code> that uses the given text 053 * storage model and the given number of columns. 054 * This is the constructor through which the other constructors feed. 055 * If the document is <code>null</code>, a default model is created. 056 * 057 * @param doc the text storage to use; if this is <code>null</code>, 058 * a default will be provided by calling the 059 * <code>createDefaultModel</code> method 060 * @param text the initial string to display, or <code>null</code> 061 * @param columns the number of columns to use to calculate 062 * the preferred width >= 0; if <code>columns</code> 063 * is set to zero, the preferred width will be whatever 064 * naturally results from the component implementation 065 * @param undoRedo Enables or not Undo/Redo feature. Not recommended for table cell editors, unless each cell provides its own editor 066 * @exception IllegalArgumentException if <code>columns</code> < 0 067 */ 068 public JosmTextField(Document doc, String text, int columns, boolean undoRedo) { 069 super(doc, text, columns); 070 TextContextualPopupMenu.enableMenuFor(this, undoRedo); 071 // Fix minimum size when columns are specified 072 if (columns > 0) { 073 setMinimumSize(getPreferredSize()); 074 } 075 addFocusListener(this); 076 // Workaround for Java bug 6322854 077 JosmPasswordField.workaroundJdkBug6322854(this); 078 } 079 080 /** 081 * Constructs a new <code>JosmTextField</code> initialized with the 082 * specified text and columns. A default model is created. 083 * 084 * @param text the text to be displayed, or <code>null</code> 085 * @param columns the number of columns to use to calculate 086 * the preferred width; if columns is set to zero, the 087 * preferred width will be whatever naturally results from 088 * the component implementation 089 */ 090 public JosmTextField(String text, int columns) { 091 this(null, text, columns); 092 } 093 094 /** 095 * Constructs a new <code>JosmTextField</code> initialized with the 096 * specified text. A default model is created and the number of 097 * columns is 0. 098 * 099 * @param text the text to be displayed, or <code>null</code> 100 */ 101 public JosmTextField(String text) { 102 this(null, text, 0); 103 } 104 105 /** 106 * Constructs a new empty <code>JosmTextField</code> with the specified 107 * number of columns. 108 * A default model is created and the initial string is set to 109 * <code>null</code>. 110 * 111 * @param columns the number of columns to use to calculate 112 * the preferred width; if columns is set to zero, the 113 * preferred width will be whatever naturally results from 114 * the component implementation 115 */ 116 public JosmTextField(int columns) { 117 this(null, null, columns); 118 } 119 120 /** 121 * Constructs a new <code>JosmTextField</code>. A default model is created, 122 * the initial string is <code>null</code>, 123 * and the number of columns is set to 0. 124 */ 125 public JosmTextField() { 126 this(null, null, 0); 127 } 128 129 /** 130 * Replies the hint displayed when no text has been entered. 131 * @return the hint 132 * @since 7505 133 */ 134 public final String getHint() { 135 return hint; 136 } 137 138 /** 139 * Sets the hint to display when no text has been entered. 140 * @param hint the hint to set 141 * @since 7505 142 */ 143 public final void setHint(String hint) { 144 this.hint = hint; 145 } 146 147 @Override 148 public void paint(Graphics g) { 149 super.paint(g); 150 if (hint != null && !hint.isEmpty() && getText().isEmpty() && !isFocusOwner()) { 151 // Taken from http://stackoverflow.com/a/24571681/2257172 152 int h = getHeight(); 153 ((Graphics2D)g).setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); 154 Insets ins = getInsets(); 155 FontMetrics fm = g.getFontMetrics(); 156 int c0 = getBackground().getRGB(); 157 int c1 = getForeground().getRGB(); 158 int m = 0xfefefefe; 159 int c2 = ((c0 & m) >>> 1) + ((c1 & m) >>> 1); 160 g.setColor(new Color(c2, true)); 161 g.drawString(hint, ins.left, h / 2 + fm.getAscent() / 2 - 2); 162 } 163 } 164 165 @Override 166 public void focusGained(FocusEvent e) { 167 if (Main.map != null) { 168 Main.map.keyDetector.setEnabled(false); 169 } 170 repaint(); 171 } 172 173 @Override 174 public void focusLost(FocusEvent e) { 175 if (Main.map != null) { 176 Main.map.keyDetector.setEnabled(true); 177 } 178 repaint(); 179 } 180}