View Javadoc
1   package org.opentrafficsim.swing.gui;
2   
3   import java.awt.Color;
4   import java.awt.event.FocusEvent;
5   import java.awt.event.FocusListener;
6   import java.beans.PropertyChangeEvent;
7   import java.beans.PropertyChangeListener;
8   
9   import javax.swing.event.DocumentEvent;
10  import javax.swing.event.DocumentListener;
11  import javax.swing.text.JTextComponent;
12  
13  /**
14   * Code taken from stack overflow. This class shows light gray text within a {@link JTextComponent} when it is empty, as a hint
15   * as to what a user might type.
16   * <p>
17   * Copyright (c) 2024-2026 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
18   * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
19   * </p>
20   * @author Wouter Schakel
21   * @see <a href=
22   *      "https://stackoverflow.com/questions/10506789/how-to-display-faint-gray-ghost-text-in-a-jtextfield">stackoverflow</a>
23   */
24  public class GhostText implements FocusListener, DocumentListener, PropertyChangeListener
25  {
26  
27      /** Text component. */
28      private final JTextComponent textComp;
29  
30      /** Whether its empty. */
31      private boolean isEmpty;
32  
33      /** Ghost color. */
34      private Color ghostColor;
35  
36      /** Regular color. */
37      private Color foregroundColor;
38  
39      /** Ghost text. */
40      private final String ghostText;
41  
42      /**
43       * Constructor.
44       * @param textComp text component to receive ghost text
45       * @param ghostText ghost text
46       */
47      public GhostText(final JTextComponent textComp, final String ghostText)
48      {
49          this.textComp = textComp;
50          this.ghostText = ghostText;
51          this.ghostColor = Color.LIGHT_GRAY;
52          textComp.addFocusListener(this);
53          registerListeners();
54          updateState();
55          if (!this.textComp.hasFocus())
56          {
57              focusLost(null);
58          }
59      }
60  
61      /**
62       * Delete ghost text.
63       */
64      public void delete()
65      {
66          unregisterListeners();
67          this.textComp.removeFocusListener(this);
68      }
69  
70      /**
71       * Register listeners.
72       */
73      private void registerListeners()
74      {
75          this.textComp.getDocument().addDocumentListener(this);
76          this.textComp.addPropertyChangeListener("foreground", this);
77      }
78  
79      /**
80       * Unregister listeners.
81       */
82      private void unregisterListeners()
83      {
84          this.textComp.getDocument().removeDocumentListener(this);
85          this.textComp.removePropertyChangeListener("foreground", this);
86      }
87  
88      /**
89       * Get ghost color.
90       * @return ghost color
91       */
92      public Color getGhostColor()
93      {
94          return this.ghostColor;
95      }
96  
97      /**
98       * Set ghost color.
99       * @param ghostColor ghost color
100      */
101     public void setGhostColor(final Color ghostColor)
102     {
103         this.ghostColor = ghostColor;
104     }
105 
106     /**
107      * Update state.
108      */
109     private void updateState()
110     {
111         this.isEmpty = this.textComp.getText().length() == 0;
112         this.foregroundColor = this.textComp.getForeground();
113     }
114 
115     @Override
116     public void focusGained(final FocusEvent e)
117     {
118         if (this.isEmpty)
119         {
120             unregisterListeners();
121             try
122             {
123                 this.textComp.setText("");
124                 this.textComp.setForeground(this.foregroundColor);
125             }
126             finally
127             {
128                 registerListeners();
129             }
130         }
131     }
132 
133     @Override
134     public void focusLost(final FocusEvent e)
135     {
136         if (this.isEmpty)
137         {
138             unregisterListeners();
139             try
140             {
141                 this.textComp.setText(this.ghostText);
142                 this.textComp.setForeground(this.ghostColor);
143             }
144             finally
145             {
146                 registerListeners();
147             }
148         }
149     }
150 
151     @Override
152     public void propertyChange(final PropertyChangeEvent evt)
153     {
154         updateState();
155     }
156 
157     @Override
158     public void changedUpdate(final DocumentEvent e)
159     {
160         updateState();
161     }
162 
163     @Override
164     public void insertUpdate(final DocumentEvent e)
165     {
166         updateState();
167     }
168 
169     @Override
170     public void removeUpdate(final DocumentEvent e)
171     {
172         updateState();
173     }
174 
175 }