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