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-2016 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 */
047public class FilterTextField extends JTextField
048{
049  private static final long serialVersionUID = -2083433734204435457L;
050  private boolean displayClearIcon;
051  private ImageIcon clearIcon = Utilities.createImageIcon(IconPool.IMAGE_PATH+
052      "/clear-filter.png");
053  private ImageIcon clearIconPressed =
054    Utilities.createImageIcon(IconPool.IMAGE_PATH+
055  "/clear-filter-down.png");
056  private ImageIcon refreshIcon =
057    UIFactory.getImageIcon(UIFactory.IconType.WAIT_TINY);
058
059  private boolean mousePressed;
060  private boolean displayRefreshIcon;
061
062  /** The time during which the refresh icon is displayed by default. */
063  public static long DEFAULT_REFRESH_ICON_TIME = 750;
064
065  private LinkedHashSet<ActionListener> listeners = new LinkedHashSet<>();
066  private boolean constructorBorderSet;
067
068  /** Default constructor. */
069  public FilterTextField()
070  {
071    super(15);
072    Border border = getBorder();
073    if (border != null)
074    {
075      setBorder(BorderFactory.createCompoundBorder(border, new IconBorder()));
076    }
077    else
078    {
079      setBorder(new IconBorder());
080    }
081    constructorBorderSet = true;
082    getDocument().addDocumentListener(new DocumentListener()
083    {
084      @Override
085      public void changedUpdate(DocumentEvent e)
086      {
087        insertUpdate(e);
088      }
089
090      @Override
091      public void insertUpdate(DocumentEvent e)
092      {
093        boolean displayIcon = getText().length() > 0;
094        if (FilterTextField.this.displayClearIcon != displayIcon)
095        {
096          FilterTextField.this.displayClearIcon = displayIcon;
097          repaint();
098        }
099      }
100      @Override
101      public void removeUpdate(DocumentEvent e)
102      {
103        insertUpdate(e);
104      }
105    });
106
107    addMouseListener(new MouseAdapter()
108    {
109      @Override
110      public void mousePressed(MouseEvent ev)
111      {
112        boolean p = getClearIconRectangle().contains(ev.getPoint());
113        if (p != mousePressed)
114        {
115          mousePressed = p;
116          repaint();
117        }
118      }
119
120      @Override
121      public void mouseReleased(MouseEvent ev)
122      {
123        if (mousePressed && getClearIconRectangle().contains(ev.getPoint()))
124        {
125          setText("");
126          notifyListeners();
127        }
128        mousePressed = false;
129      }
130    });
131  }
132
133  @Override
134  public void addActionListener(ActionListener listener)
135  {
136    listeners.add(listener);
137  }
138
139  @Override
140  public void removeActionListener(ActionListener listener)
141  {
142    listeners.remove(listener);
143  }
144
145  @Override
146  public void setBorder(Border border)
147  {
148    if (constructorBorderSet && border != null)
149    {
150      border = BorderFactory.createCompoundBorder(border, new IconBorder());
151    }
152    super.setBorder(border);
153  }
154
155  /**
156   * Displays a refresh icon on the text field (this is used for instance in
157   * the browsers that use this text field to specify a filter: the refresh
158   * icon is displayed to show that the filter is being displayed).
159   * @param display whether to display the refresh icon or not.
160   */
161  public void displayRefreshIcon(boolean display)
162  {
163    if (display != displayRefreshIcon)
164    {
165      displayRefreshIcon = display;
166      repaint();
167    }
168  }
169
170  /**
171   * Returns <CODE>true</CODE> if the refresh icon is displayed and
172   * <CODE>false</CODE> otherwise.
173   * @return <CODE>true</CODE> if the refresh icon is displayed and
174   * <CODE>false</CODE> otherwise.
175   */
176  public boolean isRefreshIconDisplayed()
177  {
178    return displayRefreshIcon;
179  }
180
181  /**
182   * Displays a refresh icon on the text field (this is used for instance in
183   * the browsers that use this text field to specify a filter: the refresh
184   * icon is displayed to show that the filter is being displayed).
185   * @param time the time (in milliseconds) that the icon will be displayed.
186   */
187  public void displayRefreshIcon(final long time)
188  {
189    displayRefreshIcon = true;
190    repaint();
191    Thread t = new Thread(new Runnable()
192    {
193      @Override
194      public void run()
195      {
196        try
197        {
198          Thread.sleep(time);
199        }
200        catch (Throwable t)
201        {
202        }
203        finally
204        {
205          SwingUtilities.invokeLater(new Runnable()
206          {
207            @Override
208            public void run()
209            {
210              displayRefreshIcon = false;
211              repaint();
212            }
213          });
214        }
215      }
216    });
217    t.start();
218  }
219
220  private static int id = 1;
221  private void notifyListeners()
222  {
223    ActionEvent ev = new ActionEvent(this, id,
224        "CLEAR_FILTER");
225    id ++;
226    for (ActionListener listener : listeners)
227    {
228      listener.actionPerformed(ev);
229    }
230  }
231
232  private Rectangle getClearIconRectangle()
233  {
234    ImageIcon icon = getClearIcon();
235    int margin = getMargin(this, icon);
236    return new Rectangle(getWidth() - margin - icon.getIconWidth(),
237        margin, icon.getIconWidth(), icon.getIconHeight());
238  }
239
240  /** The border of this filter text field. */
241  private class IconBorder implements Border
242  {
243    @Override
244    public Insets getBorderInsets(Component c)
245    {
246      ImageIcon icon = getClearIcon();
247      int rightInsets = 0;
248      if (displayClearIcon)
249      {
250        rightInsets += icon.getIconWidth() + getMargin(c, icon);
251      }
252      if (displayRefreshIcon)
253      {
254        rightInsets += refreshIcon.getIconWidth() + getMargin(c, refreshIcon);
255      }
256      return new Insets(0, 0, 0, rightInsets);
257    }
258
259    @Override
260    public void paintBorder(Component c, Graphics g, int x, int y,
261        int width, int height)
262    {
263      if (displayClearIcon || displayRefreshIcon)
264      {
265        Graphics2D g2d = (Graphics2D) g.create();
266        int leftSpaceOfClearIcon = 0;
267        if (displayClearIcon)
268        {
269          ImageIcon icon = getClearIcon();
270          int margin = (height - icon.getIconHeight()) / 2;
271          icon.paintIcon(c,
272              g2d, x + width - margin - icon.getIconWidth(),
273              y + margin);
274          leftSpaceOfClearIcon = margin + icon.getIconWidth();
275        }
276        if (displayRefreshIcon)
277        {
278          int margin = (height - refreshIcon.getIconHeight()) / 2;
279          refreshIcon.paintIcon(c, g2d, x + width - margin -
280              refreshIcon.getIconWidth() - leftSpaceOfClearIcon, y + margin);
281        }
282        g2d.dispose(); //clean up
283      }
284    }
285
286    @Override
287    public boolean isBorderOpaque()
288    {
289      return false;
290    }
291  }
292  private int getMargin(Component c, ImageIcon icon)
293  {
294    return (c.getHeight() - icon.getIconHeight()) / 2;
295  }
296
297  private ImageIcon getClearIcon()
298  {
299    return mousePressed ? clearIconPressed : clearIcon;
300  }
301}