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 (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 * <br>This class must be used everywhere in core and plugins instead of {@code JTextField}. 024 * @since 5886 025 */ 026public class JosmTextField extends JTextField implements FocusListener { 027 028 private String hint; 029 030 /** 031 * Constructs a new <code>JosmTextField</code> that uses the given text 032 * storage model and the given number of columns. 033 * This is the constructor through which the other constructors feed. 034 * If the document is <code>null</code>, a default model is created. 035 * 036 * @param doc the text storage to use; if this is <code>null</code>, 037 * a default will be provided by calling the 038 * <code>createDefaultModel</code> method 039 * @param text the initial string to display, or <code>null</code> 040 * @param columns the number of columns to use to calculate 041 * the preferred width >= 0; if <code>columns</code> 042 * is set to zero, the preferred width will be whatever 043 * naturally results from the component implementation 044 * @exception IllegalArgumentException if <code>columns</code> < 0 045 */ 046 public JosmTextField(Document doc, String text, int columns) { 047 super(doc, text, columns); 048 TextContextualPopupMenu.enableMenuFor(this); 049 // Fix minimum size when columns are specified 050 if (columns > 0) { 051 setMinimumSize(getPreferredSize()); 052 } 053 addFocusListener(this); 054 } 055 056 /** 057 * Constructs a new <code>JosmTextField</code> initialized with the 058 * specified text and columns. A default model is created. 059 * 060 * @param text the text to be displayed, or <code>null</code> 061 * @param columns the number of columns to use to calculate 062 * the preferred width; if columns is set to zero, the 063 * preferred width will be whatever naturally results from 064 * the component implementation 065 */ 066 public JosmTextField(String text, int columns) { 067 this(null, text, columns); 068 } 069 070 /** 071 * Constructs a new <code>JosmTextField</code> initialized with the 072 * specified text. A default model is created and the number of 073 * columns is 0. 074 * 075 * @param text the text to be displayed, or <code>null</code> 076 */ 077 public JosmTextField(String text) { 078 this(null, text, 0); 079 } 080 081 /** 082 * Constructs a new empty <code>JosmTextField</code> with the specified 083 * number of columns. 084 * A default model is created and the initial string is set to 085 * <code>null</code>. 086 * 087 * @param columns the number of columns to use to calculate 088 * the preferred width; if columns is set to zero, the 089 * preferred width will be whatever naturally results from 090 * the component implementation 091 */ 092 public JosmTextField(int columns) { 093 this(null, null, columns); 094 } 095 096 /** 097 * Constructs a new <code>JosmTextField</code>. A default model is created, 098 * the initial string is <code>null</code>, 099 * and the number of columns is set to 0. 100 */ 101 public JosmTextField() { 102 this(null, null, 0); 103 } 104 105 /** 106 * Replies the hint displayed when no text has been entered. 107 * @return the hint 108 * @since 7505 109 */ 110 public final String getHint() { 111 return hint; 112 } 113 114 /** 115 * Sets the hint to display when no text has been entered. 116 * @param hint the hint to set 117 * @since 7505 118 */ 119 public final void setHint(String hint) { 120 this.hint = hint; 121 } 122 123 @Override 124 public void paint(Graphics g) { 125 super.paint(g); 126 if (hint != null && !hint.isEmpty() && getText().isEmpty() && !isFocusOwner()) { 127 // Taken from http://stackoverflow.com/a/24571681/2257172 128 int h = getHeight(); 129 ((Graphics2D)g).setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); 130 Insets ins = getInsets(); 131 FontMetrics fm = g.getFontMetrics(); 132 int c0 = getBackground().getRGB(); 133 int c1 = getForeground().getRGB(); 134 int m = 0xfefefefe; 135 int c2 = ((c0 & m) >>> 1) + ((c1 & m) >>> 1); 136 g.setColor(new Color(c2, true)); 137 g.drawString(hint, ins.left, h / 2 + fm.getAscent() / 2 - 2); 138 } 139 } 140 141 @Override 142 public void focusGained(FocusEvent e) { 143 if (Main.map != null) { 144 Main.map.keyDetector.setEnabled(false); 145 } 146 repaint(); 147 } 148 149 @Override 150 public void focusLost(FocusEvent e) { 151 if (Main.map != null) { 152 Main.map.keyDetector.setEnabled(true); 153 } 154 repaint(); 155 } 156}