001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions Copyright [year] [name of copyright owner]".
013 *
014 * Copyright 2008 Sun Microsystems, Inc.
015 * Portions Copyright 2015 ForgeRock AS.
016 */
017
018package org.opends.guitools.controlpanel.ui.components;
019
020import java.awt.Component;
021import java.awt.Graphics;
022import java.awt.Graphics2D;
023import java.awt.Insets;
024import java.awt.Rectangle;
025import java.awt.event.ActionEvent;
026import java.awt.event.ActionListener;
027import java.awt.event.MouseAdapter;
028import java.awt.event.MouseEvent;
029import java.util.LinkedHashSet;
030
031import javax.swing.BorderFactory;
032import javax.swing.ImageIcon;
033import javax.swing.JTextField;
034import javax.swing.SwingUtilities;
035import javax.swing.border.Border;
036import javax.swing.event.DocumentEvent;
037import javax.swing.event.DocumentListener;
038
039import org.opends.guitools.controlpanel.browser.IconPool;
040import org.opends.guitools.controlpanel.util.Utilities;
041import org.opends.quicksetup.ui.UIFactory;
042
043/**
044 * A text field with an icon with 'X' shape on the right.  When the user clicks
045 * on that icon, the contents of the text field are cleared.
046 *
047 */
048public class FilterTextField extends JTextField
049{
050  private static final long serialVersionUID = -2083433734204435457L;
051  private boolean displayClearIcon;
052  private ImageIcon clearIcon = Utilities.createImageIcon(IconPool.IMAGE_PATH+
053      "/clear-filter.png");
054  private ImageIcon clearIconPressed =
055    Utilities.createImageIcon(IconPool.IMAGE_PATH+
056  "/clear-filter-down.png");
057  private ImageIcon refreshIcon =
058    UIFactory.getImageIcon(UIFactory.IconType.WAIT_TINY);
059
060  private boolean mousePressed;
061  private boolean displayRefreshIcon;
062
063  /**
064   * The time during which the refresh icon is displayed by default.
065   */
066  public static long DEFAULT_REFRESH_ICON_TIME = 750;
067
068  private LinkedHashSet<ActionListener> listeners = new LinkedHashSet<>();
069  private boolean constructorBorderSet;
070
071  /** Default constructor. */
072  public FilterTextField()
073  {
074    super(15);
075    Border border = getBorder();
076    if (border != null)
077    {
078      setBorder(BorderFactory.createCompoundBorder(border, new IconBorder()));
079    }
080    else
081    {
082      setBorder(new IconBorder());
083    }
084    constructorBorderSet = true;
085    getDocument().addDocumentListener(new DocumentListener()
086    {
087      /** {@inheritDoc} */
088      public void changedUpdate(DocumentEvent e)
089      {
090        insertUpdate(e);
091      }
092
093      /** {@inheritDoc} */
094      public void insertUpdate(DocumentEvent e)
095      {
096        boolean displayIcon = getText().length() > 0;
097        if (FilterTextField.this.displayClearIcon != displayIcon)
098        {
099          FilterTextField.this.displayClearIcon = displayIcon;
100          repaint();
101        }
102      }
103      public void removeUpdate(DocumentEvent e)
104      {
105        insertUpdate(e);
106      }
107    });
108
109    addMouseListener(new MouseAdapter()
110    {
111      /** {@inheritDoc} */
112      public void mousePressed(MouseEvent ev)
113      {
114        boolean p = getClearIconRectangle().contains(ev.getPoint());
115        if (p != mousePressed)
116        {
117          mousePressed = p;
118          repaint();
119        }
120      }
121
122      /** {@inheritDoc} */
123      public void mouseReleased(MouseEvent ev)
124      {
125        if (mousePressed && getClearIconRectangle().contains(ev.getPoint()))
126        {
127          setText("");
128          notifyListeners();
129        }
130        mousePressed = false;
131      }
132    });
133  }
134
135  /**
136   * Adds an action listener to this text field.  When the user clicks on the
137   * 'X' shaped icon the listeners are notified.
138   * @param listener the action listener.
139   */
140  public void addActionListener(ActionListener listener)
141  {
142    listeners.add(listener);
143  }
144
145  /**
146   * Removes an action listener to this text field.
147   * @param listener the action listener.
148   */
149  public void removeActionListener(ActionListener listener)
150  {
151    listeners.remove(listener);
152  }
153
154  /** {@inheritDoc} */
155  public void setBorder(Border border)
156  {
157    if (constructorBorderSet && border != null)
158    {
159      border = BorderFactory.createCompoundBorder(border, new IconBorder());
160    }
161    super.setBorder(border);
162  }
163
164  /**
165   * Displays a refresh icon on the text field (this is used for instance in
166   * the browsers that use this text field to specify a filter: the refresh
167   * icon is displayed to show that the filter is being displayed).
168   * @param display whether to display the refresh icon or not.
169   */
170  public void displayRefreshIcon(boolean display)
171  {
172    if (display != displayRefreshIcon)
173    {
174      displayRefreshIcon = display;
175      repaint();
176    }
177  }
178
179  /**
180   * Returns <CODE>true</CODE> if the refresh icon is displayed and
181   * <CODE>false</CODE> otherwise.
182   * @return <CODE>true</CODE> if the refresh icon is displayed and
183   * <CODE>false</CODE> otherwise.
184   */
185  public boolean isRefreshIconDisplayed()
186  {
187    return displayRefreshIcon;
188  }
189
190  /**
191   * Displays a refresh icon on the text field (this is used for instance in
192   * the browsers that use this text field to specify a filter: the refresh
193   * icon is displayed to show that the filter is being displayed).
194   * @param time the time (in miliseconds) that the icon will be displayed.
195   *
196   */
197  public void displayRefreshIcon(final long time)
198  {
199    displayRefreshIcon = true;
200    repaint();
201    Thread t = new Thread(new Runnable()
202    {
203      public void run()
204      {
205        try
206        {
207          Thread.sleep(time);
208        }
209        catch (Throwable t)
210        {
211        }
212        finally
213        {
214          SwingUtilities.invokeLater(new Runnable()
215          {
216            public void run()
217            {
218              displayRefreshIcon = false;
219              repaint();
220            }
221          });
222        }
223      }
224    });
225    t.start();
226  }
227
228  private static int id = 1;
229  private void notifyListeners()
230  {
231    ActionEvent ev = new ActionEvent(this, id,
232        "CLEAR_FILTER");
233    id ++;
234    for (ActionListener listener : listeners)
235    {
236      listener.actionPerformed(ev);
237    }
238  }
239
240  private Rectangle getClearIconRectangle()
241  {
242    ImageIcon icon = getClearIcon();
243    int margin = getMargin(this, icon);
244    return new Rectangle(getWidth() - margin - icon.getIconWidth(),
245        margin, icon.getIconWidth(), icon.getIconHeight());
246  }
247
248  /**
249   * The border of this filter text field.
250   *
251   */
252  private class IconBorder implements Border
253  {
254    /** {@inheritDoc} */
255    public Insets getBorderInsets(Component c)
256    {
257      ImageIcon icon = getClearIcon();
258      int rightInsets = 0;
259      if (displayClearIcon)
260      {
261        rightInsets += icon.getIconWidth() + getMargin(c, icon);
262      }
263      if (displayRefreshIcon)
264      {
265        rightInsets += refreshIcon.getIconWidth() + getMargin(c, refreshIcon);
266      }
267      return new Insets(0, 0, 0, rightInsets);
268    }
269
270    /** {@inheritDoc} */
271    public void paintBorder(Component c, Graphics g, int x, int y,
272        int width, int height)
273    {
274      if (displayClearIcon || displayRefreshIcon)
275      {
276        Graphics2D g2d = (Graphics2D) g.create();
277        int leftSpaceOfClearIcon = 0;
278        if (displayClearIcon)
279        {
280          ImageIcon icon = getClearIcon();
281          int margin = (height - icon.getIconHeight()) / 2;
282          icon.paintIcon(c,
283              g2d, x + width - margin - icon.getIconWidth(),
284              y + margin);
285          leftSpaceOfClearIcon = margin + icon.getIconWidth();
286        }
287        if (displayRefreshIcon)
288        {
289          int margin = (height - refreshIcon.getIconHeight()) / 2;
290          refreshIcon.paintIcon(c, g2d, x + width - margin -
291              refreshIcon.getIconWidth() - leftSpaceOfClearIcon, y + margin);
292        }
293        g2d.dispose(); //clean up
294      }
295    }
296
297    /** {@inheritDoc} */
298    public boolean isBorderOpaque()
299    {
300      return false;
301    }
302  }
303  private int getMargin(Component c, ImageIcon icon)
304  {
305    return (c.getHeight() - icon.getIconHeight()) / 2;
306  }
307
308  private ImageIcon getClearIcon()
309  {
310    return mousePressed ? clearIconPressed : clearIcon;
311  }
312}